一.创建对象
虽然Object构造函数或对象字面量可以用来创建单个对象,但有个明显缺点:使用同一个接口创建很多对象会产生大量重复代码。因而大家开始探索其他方式。
1.工厂模式
function createPerson (name, age) {
var o = new Object():
o.name = name;
o.age = age;
o.sayName = function () {
console.log(this.name);
}
return o;
}
var person = createPerson('Lily', 12);
工厂模式特点:虽然解决了创建多个相似对象的问题,但却无法识别一个对象的类型。(instance of)
2.构造函数模式
function Person (name, age) {
this.name = name;
this.age = age;
this.sayName = function () {
console.log(this.name);
}
}
var person = new Person('Lily', 12); //使用构造函数模式创建实例,必须使用new操作符。
构造函数模式特点:可以将它的实例标识为一种特定类型,但每个方法都要在实力上重新创建一遍。
3.原型模式
function Person () {}
Person.prototype.name = 'Lily';
Person.prototype.age = 12;
Person.prototype.sayName = function () {
console.log(this.name);
}
var person = new Person(); //使用hasOwnProperty方法可以检测一个属性存在于实例还是原型。
console.log(person.hasOwnProperty('name')); //false
person.name = 'Tom'; //来自实例
console.log(person.hasOwnProperty('name')); //true
更简单的原型语法:
function Person () {}
Person.prototype = {
name: 'Lily',
age: 12,
sayName: function () {
console.log(this.name);
}
} //这样写会导致constructor属性不再指向Person了。(无论何时创建一个新函数A,都会为它创建一个prototype属性,指向函数的原型对象,默认情况下,所有原型对象都会获得一个constructor属性,包含一个指向A的指针。)
因而可更改为:
function Person () {}
Person.prototype = {
constructor: Person, //增加constructor属性
name: 'Lily',
age: 12,
sayName: function () {
console.log(this.name);
}
} //这种写法要注意,创建实例一定要在定义原型之后,因为重写原型对象就切断了构造函数与最初原型的联系。
原型模式的特点:实现了让所有实例共享原型对象所包含的属性和方法。但缺点也在于这种共享对于包含引用类型值的属性而言,存在一些问题,即所有实例会共享一个数组或者对象。
4.组合构造函数模式和原型模式
function Person (name, age) {
this.name = name;
this.age = age;
this.friends = ['Tom', 'Bob'];
}
Person.prototype = {
constructor: Person,
sayName: function () {
console.log(this.name);
}
}
构造函数模式用于定义实例属性,而原型模式用于定义方法和共享的属性。这是目前最广泛,认同度最高的方法。
5.动态原型模式
有其他OO语言经验的开发人员看到独立的构造函数和原型,可能会困惑。动态原型模式则是致力于解决此问题的一个方案,它把所有信息都封装在了构造函数中,在必要情况下,通过在构造函数中初始化原型,保持了组合使用构造函数和原型的优点。
function Person (name, age) {
this.name = name;
this.age = age;
this.friends = ['Tom', 'Bob'];
if (type of this.sayName != 'function') {
Person.prototype.sayName = function () {
console.log(this.name);
}
} //if语句只需检查一个初始化应该存在的共享属性或方法即可。
}
使用动态原型模式要记得不能用对象字面量重写原型,因为如果在创建了实例的情况下重写原型。那么就会切断现有实例与新原型的联系。
关于寄生构造模式和稳妥构造模式,在工程实践用的不多且稍显过时,就不赘述了。有时间可以了解es6的class。
二.继承
1.原型链
function SuperType(){
this.property = true;
}
SuperType.prototype.getSuperValue = function () {
return this.property;
}
function SubType(){
this.property = false;
}
SubType.prototype = new SuperType();
SubType.prototype.getSubValue = function () {
return this.property;
}
需要注意的是,在通过原型链实现继承时,不能用对象字面量创建原型方法,因为这样会重写原型链。如下所示:
function SuperType(){
this.property = true;
}
SuperType.prototype.getSuperValue = function () {
return this.property;
}
function SubType(){
this.property = false;
}
SubType.prototype = new SuperType();
SubType.prototype = {
getSubValue: function () {
return this.subproperty;
},
someOtherMethod: function () {
return false;
}
} //这样会导致SubType的实例无法访问到getSuperValue()方法了。
原型链继承的问题:
1.共享实例属性,如果SupeType中定义一个数组colors,当subType通过原型链继承SuperType后,它也会拥有一个colors属性(就像专门创建了一个subType.prototype.colors属性一样),结果是SubType所有实例都会共享这个属性。对其中一个实例.colors属性会影响到所有其他实例。
2.在创建子类型的实例时,没办法在不影响所有对象实例的情况下,向超类型的构造函数传递参数。
因此实践中很少单独使用原型链继承。
2.借用构造函数
function SuperType (name) {
this.name = name;
this.colors = ['red', 'blue', 'yellow'];
}
function SubType ()
SuperType.call(this, 'Lily');
this.age = 12;
} //使用这种方式就可以向超类型的构造函数传参啦。
借用构造函数的问题:还是和构造函数创建对象一样,方法都在构造函数定义,函数复用就无从谈起了。
3.组合继承
使用原型链实现对原型属性和方法的继承,而通过构造函数实现对实例属性的继承。
function SuperType (name) {
this.name = name;
this.colors = ['red', 'blue', 'yellow'];
}
SuperType.prototype.sayName = function () {
console.log(this.name);
};
function SubType (name, age)
// 继承实例属性
SuperType.call(this, name); //第二次调用SuperType()
this.age = age;
}
//继承方法
SubType.prototype = new SuperType(); //第一次调用SuperType()
SubType.prototype.constructor = SubType;
SubType.prototype.sayAge = function () {
console.log(this.age);
}
var instance1 = new SubType('Lily', 12);
instance1.colors.push('green');
console.log(instance1.colors); // "red,blue,yellow,green"
var instance2 = new SubType('Lucy', 22);
console.log(instance1.colors); // "red,blue,yellow"
组合继承避免了原型链和借用构造函数的缺陷,是一种较为流行的继承方式。但是它还存在一个缺点:在第一次调用superType时,SubType.prototype会得到两个属性:name和colors,当调用SubType的构造函数时又会再调用一次SuperType构造函数,在对象实例上创建了实例属性name和colors屏蔽了SubType原型中的同名属性。
寄生组合式继承
寄生组合式是组合式继承的改进,思路是不必为了指定子类的原型而调用超类的构造函数,仅复制超类的原型即可。
function SuperType (name) {
this.name = name;
this.colors = ['red', 'blue', 'yellow'];
}
SuperType.prototype.sayName = function () {
console.log(this.name);
};
function SubType (name, age)
// 继承实例属性
SuperType.call(this, name);
this.age = age;
}
//继承方法
SubType.prototype = Object.create(SuperType.prototype);
//Object.create(obj)相当于 function F(){}; F.prototype = obj;return new F();
SubType.prototype.constructor = SubType;
SubType.prototype.sayAge = function () {
console.log(this.age);
}
开发人员普遍认为寄生组合式继承是引用类型最理想的继承方法。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。