说起面向对象,大部分程序员首先会想到 类 。通过类可以创建许多具有共同属性以及方法的实例或者说对象。但是JavaScript并没有类的概念,而且在JavaScript中几乎一切皆对象,问题来了,JavaScript中如何面向对象?

  JavaScript中将对象定义为:一组无序的 键值对的集合,属性以及方法的名称就是键,键的值可以是任何类型(字符串,数字,函数……)

  在JavaScript中,所有对象继承自Object,所有对象继承自Object,所有对象继承自Object!

创建

1  简单创建对象

    var o = new Object();
o.name = 'mncu';
o.age = 120;
o.sayName = function(){
alert(this.name);
};
o.sayName(); // 'mncu' // 字面量创建 与上面的代码等价
var o = {
name:'mncu',
age : 120,
sayName:function(){
alert(this.name);
} };
o.sayName(); //'mncu'

使用上面的方法虽然可以创建对象,但缺点也很明显,假如再次创建一个具有相同属性以及方法的对象,还得把代码复制修改一遍,会产生大量的重复代码。

2  工厂模式

  这种模式抽象了创建对象的具体过程

function createperson(name,age){
var o = new Object();
o.name = name;
o.age = age;
o.sayName = function(){
alert(this.name);
};
return o; }
var p1 = createperson('mncu',120);
var p2 = createperson('linghuchong',120);
p1.sayName(); //'mncu'
p2.sayName(); //'linghuchong'
console.log(typeof p1); //object
p1 instanceof createperson // false ,无法判断类型

但这种方式有一个缺点:无法判断某个对象是什么类型。

3  构造函数模式

function Person(name,age){   // 按照惯例,构造函数的首字母要大写
this.name = name;
this.age = age;
this.sayName = function(){
alert(this.name);
};
}
var p1 = new Person('mncu',120); // 必须使用new关键字创建对象
var p2 = new Person('linghuchong',120);
p1.sayName(); //'mncu'
alert(p1 instanceof Person); // true,可判断类型

构造函数模式解决了工厂模式中不能判断对象类型的问题。

在使用构造函数模式时要注意:

  --必须使用new关键字创建对象! new 关键字相当于做了以下几步:

    1  创建一个新对象

    2  将构造函数的作用域赋值给这个新对象(因此this就指向了这个新对象)

    3  执行构造函数的代码(为这个新对象添加属性)

    4  返回新对象

  --使用构造函数创建的对象都有一个constructor属性,该属性可以标识对象类型,但一般我们还是常用instanceof来判断对象类型, 因为constructor属性仅返回构造函数类型。

p1.constructor === Person  //true
p1.constructor === Object //false
p1 instanceof Object  // true
p1 instanceof Person //true

  --构造函数实际上和普通函数并无多大区别,因为其不存在特殊的语法,任何函数,只要通过new调用,那么他就可以作为构造函数。

var p3 = Person('dongfangbubai',120);  // 不使用new时,函数的作用域会是全局作用域,this就会指向window对象。
p3.sayName() //报错
sayName() // 'dongfangbubai'

  --构造函数也存在问题,每个方法都要在实例上创建一遍。也就是说p1和p2的sayName()方法虽然作用相同,但这两个方法并不是同一个函数。

p1.sayName == p2.sayName  // false

  --无new创建:

function Person(name,age){
//alert(this);
if(!(this instanceof Person)){
return new Person(name,age);
}
this.name = name;
this.age = age;
this.sayName = function(){
alert(this.name);
}
}
var p1 = Person('hah',20);
console.log(p1.name)

4  原型模式

  原型模式解决了构造函数模式中同功能的方法的代码无法重用的问题。

  我们创建的每个函数都有一个名为prototype的属性,这个属性是一个指针,指向一个对象,这个对象被称为原型对象。原型对象有一个名叫constructor的属性,这个属性是一个指针,指向构造函数。默认情况下,所有函数的原型都是Object的实例。

使用原型模式创建对象:

function Person(){
Person.prototype.name = 'noOne';
Person.prototype.age = 'noAge';
Person.prototype.sayName = function(){
return this.name;
}; }
var p1 = new Person();
p1.name; // 'noOne'
var p2 = new Person()
p2.name; //'noOne'

在本例中,构造函数创建的对象p1的name属性是如何展现的?

  首先p1会查找自身有没有name属性,如果有的话,就返回自身的name属性的值,如果没有的话,则查找原型对象中有没有name属性,若原型对象中有name属性,则返回其值,否则,就报错。

p1.name = 'mncu';
p1.name; // 'mncu'
p2.name; //'noOne'

我们可以通过hasOwnProperty()方法检测一个属性是存在于具体的对象中,还是存在于该对象的原型对象中

p1.hasOwnProperty('name')  //true
p2.hasOwnProperty('name') //false

我们也可以通过 in 关键字来判断一个属性是否存在于具体的对象或者该对象的原型对象中

'name' in p1  // true
'name' in p2 // true

原型对象的简写格式:

    function Person(){

    }
Person.prototype={ // 将原型对象重写,但重写后原型对象的constructor就不会指向Person了(指向Object)。所以我们一般会添加:constructor:Person
constructor:Person,
name:'noOne',
age:'noAge',
sayName:function(){
alert(this.name);
}
};

原型模式存在的问题

    function Person(){

    }
Person.prototype={
constructor:Person,
name:'noOne',
age:'noAge',
brothers : ['xiaoming'],
sayName:function(){
alert(this.name);
}
};
var p1 = new Person();
var p2 = new Person();
p1.brothers.push('xiaohong');
console.log(p2.brothers) // ['xiaoming','xiaohong']

当我们改变 值为引用类型的对象的属性 时,这个改变的结果会被其他对象共享。

5  将构造函数模式和原型模式结合

  构造函数模式的属性没毛病。缺点是:无法共享方法

  原型模式的方法没毛病。缺点是:当原形对象的属性的值为引用类型时,对其进行修改会反映到所有实例中 

  那我们就将两者的结合,对象的属性使用构造函数模式创建,方法则使用原型模式创建

这种方式是最为常见的一种面向对象编程模式

function Person(name,age){
this.name = name;
this.age = age; }
Person.prototype={
constructor:Person,
sayName:function(){
alert(this.name);
}
};
var p1 = new Person('mncu',120);
p1.name; // 'mncu'

继承

1  原型链

  JavaScript中引入了原型链的概念,具体思想: 子构造函数的原型对象初始化为父构造函数的实例,孙构造函数的原型对象初始化为子构造函数的实例…… ,这样子对象就可以通过原型链一级一级向上查找,访问父构造函数中的属性以及方法。

构建原型链:

    function Animal(name){
this.name = name || 'null';
this.action = ['move','wow'];
}
function Cat(){
this.wow = function miao(){
alert('miao');
};
}
function Dog(){
this.wow = function(){
alert('wang')
};
}
Cat.prototype = new Animal();
Dog.prototype = new Animal();
var c1 = new Cat();
var d1 = new Dog();
console.log(c1.action); // ['move','wow']
d1.action.push('run');
var c2 = new Cat();
var d2 = new Dog();
console.log(d1.action); //["move", "wow", "run"]
console.log(c1.action); //["move", "wow"]
console.log(c2.action); //["move", "wow"]
console.log(d2.action); //["move", "wow", "run"]

原型链继承的问题:

    function SuperObject(){
this.colors = ['red','blue'];
}
function SubObject(){ }
SubObject.prototype = new SuperObject(); var instance1 = new SubObject();
instance1.colors.push('yellow');
var instance2 = new SubObject();
console.log(instance2.colors) // ["red", "blue", "yellow"]

当我们改变 值为引用类型的原型对象的属性 时,这个改变的结果会被所有子对象共享。这个缺点某些时候相当致命,所以我们很少使用这种方法来继承

2  借用构造函数继承

    function SuperObject(){
this.colors = ['red','blue'];
this.sayBye= function(){
console.log('Bye')
}
}
function SubObject(){
SuperObject.call(this); // 在子类中调用父类的构造方法,实际上子类和父类已经没有上下级关系了
} var instance1 = new SubObject();
instance1.colors.push('yellow');
var instance2 = new SubObject();
console.log(instance2.colors); //['red','blue']
console.log(instance2 instanceof SuperObject); // false
console.log(instance1.sayBye === instance2.sayBye) // false

这个方法虽然弥补了原型链的缺点,但是又暴露出了新的缺点:

  1  子类和父类没有上下级关系,instance2 instanceof SuperObject    结果是false

  2  父类中的方法在每个子类中都会生成一遍,父类中的方法没有被复用。

3  组合继承

  组合继承就是将原型链继承和借用构造方法继承组合,发挥两者之长。

    function SuperObject(){
this.colors = ['red','blue']; } SuperObject.prototype.sayBye= function(){
console.log('bye')
}; function SubObject(){
// 引用父类型的属性,又调用了一次父函数
SuperObject.call(this);
}
// 继承父类型的方法,调用了一次父函数
SubObject.prototype = new SuperObject(); var instance1 = new SubObject();
instance1.colors.push('yellow');
var instance2 = new SubObject();
console.log(instance2.colors); //['red','blue']
console.log(instance2 instanceof SuperObject); // true
console.log(instance1.sayBye === instance2.sayBye); // true

4  寄生组合式继承----道格拉斯方法

  虽然组合继承没啥大缺点,但是爱搞事情的有强迫症的程序猿们觉得,组合继承会调用两次父类型函数(在上面的代码中标注了),不够完美。于是道格拉斯就提出了寄生组合继承。

思路是构造一个中间函数,将中间函数的prototype指向父函数的原型对象,将子函数的prototype指向中间函数,并将中间函数的constructor属性指向子函数。

function inherits(Child, Parent) {
var F = function () {};
F.prototype = Parent.prototype;
Child.prototype = new F();
Child.prototype.constructor = Child;
}
function Student(props) {
this.name = props.name || 'Unnamed';
} Student.prototype.hello = function () {
alert('Hello, ' + this.name + '!');
} function PrimaryStudent(props) {
Student.call(this, props);
this.grade = props.grade || 1;
} // 实现原型继承链:
inherits(PrimaryStudent, Student); // 绑定其他方法到PrimaryStudent原型:
PrimaryStudent.prototype.getGrade = function () {
return this.grade;
};

这个方法只调用了一次父构造函数,并因此避免了在父函数的原型对象上创建不必要的、多余的属性。

开发人员都表示:这种方法是最理想的继承方式(我没看出来,只觉得这是最烧脑的继承方式,看来我离开发人员还有一定距离。。。)

JavaScript高级程序设计笔记之面向对象的更多相关文章

  1. javascript高级编程笔记05(面向对象)

    面向对象设计 es中有两种属性:数据属性和访问器属性 数据属性: 数据属性包含一个数据值的位置,在这个位置可以读取和写入值,数据属性有4个描述其行为的特性 [[Configurable]]:表示能否通 ...

  2. javascript高级程序设计--笔记01

    概述 JavaScript的实现包含三个部分: 1  核心(ECMAScript)   提供核心语言功能 2  文档对象模型(DOM)  一套提供了访问以及操作网页内容的API 3  浏览器对象模型( ...

  3. javascript事件小结(事件处理程序方式)--javascript高级程序设计笔记

    1.事件流:描述的是从页面中接收事件的顺序. 2.事件冒泡:IE的事件流叫做事件冒泡,即事件开始从具体的元素(文档中嵌套层次最深的那个节点)接收,然后逐级向上传播到不具体的节点(文档). 3.事件捕获 ...

  4. JavaScript高级程序设计笔记(一)

    ---恢复内容开始--- 前三章为基础知识,为了方便以后查看,所以比较啰嗦.这里对函数的基本操作没有记录. 1.JavaScript的实现 虽然 JavaScript 和 ECMAScript 通常都 ...

  5. JavaScript高级程序设计笔记 事件冒泡和事件捕获

    1.事件冒泡 要理解事件冒泡,就得先知道事件流.事件流描述的是从页面接收事件的顺序,比如如下的代码: <body> <div> click me! </div> & ...

  6. <javascript高级程序设计>笔记

    1.要讲一个值转换成其对应的Boolean类型 ,可以调用转型函数Boolean(). var message=“hello world!”; var messageAsBoolean=Boolean ...

  7. javaScript高级程序设计笔记 2

    Undefinde Null Boolean Number String    基本类型 Object    引用类型 只有引用类型才能动态的添加属性 赋值基本类型和引用类型也不相同,复制的基本类型的 ...

  8. javaScript高级程序设计笔记 1

    核心  ECMAScript 文档对象模型  DOM 浏览器对象模型 BOM 延迟脚本  defer typeof操作符      判断字符类型  返回   undefined  boolean  s ...

  9. Javascript高级程序设计笔记 <第五章> 引用类型

    一.object类型 创建object实例的方式有两种: //第一种使用new操作符跟构造函数 var person= new Object(); person.name="小王" ...

随机推荐

  1. HDU 3006

    http://acm.hdu.edu.cn/showproblem.php?pid=3006 注意到集合内数字最大只有14,状态压缩一下,然后枚举出所有状态 #include <iostream ...

  2. Conntect Bluetooth devices in iOS.

    I understand that the External Accessory framework in iOS 3.0 and later will allow my application to ...

  3. JS 点击复制Copy (share)

    分享自:http://www.cnblogs.com/athens/archive/2013/01/16/2862981.html 1.实现点击按钮,复制文本框中的的内容 1 <script t ...

  4. Egit Patch

    Git为我们提供了Patch功能,Patch中包含了源码更改的描述,能够应用于其他Eclipse工作空间或者Git仓库.也就是说,可以将当前提交导出至其他分支或者项目中. 举个例子,项目A.B中使用了 ...

  5. Linux命令--top使用技巧

    摘自 http://www.jb51.net/LINUXjishu/151995.html top命令是Linux下常用的性能分析工具,能够实时显示系统中各个进程的资源占用情况,类似于Windows的 ...

  6. Objective-c——UI基础开发第十一天(UICollectionView)

    一.知识点 1.UICollectionView的dataSource .delegate 2.UICollectionView多组数据和单组数据的展示 3.UICollectionView.UICo ...

  7. uva 11582

    #include <iostream> #include <map> #include <cmath> #include <vector> #inclu ...

  8. 在笔记本电脑开通无线WIFI

    1.Windows + R启动运行,输入services.msc进入服务 2.在服务中将Security Center服务从自动启动转为禁止启动 3.在服务中将Windows Firewall的启动类 ...

  9. 没接触C++之前与学习了C++之后的思想转变

    我在学习C++之前学习了C,学习C是因为选修时觉得它比较神奇,当时以为学会了C就能纵横计算机领域. 之后听说C++更厉害.并且大多数我这样的男生都喜欢玩游戏,C++又是能编写大型游戏逻辑的语言.于是幻 ...

  10. hmtl 中的定位

    1.绝对定位: position:sbsolute: 作用:将元素从文档流中拖出来,然后使用 left,right,top,bottom属性相对于其最接近的一个具有定位属性的父包含块进行绝对定位. 若 ...