本文是基于高级程序设计一书中原型和继承章节基础上做的总结。
ECMAScript实现继承的主要方式是依靠原型链。基本思想是利用原型让一个引用类型继承另一个引用类型的属性和方法,是继承对象的原型=被继承对象的实例,因为实例的[[Prototype]]指向被继承对象的原型,所以继承对象的原型可以指向被继承对象的原型。
原型继承
借用高级程序设计的书中代码如下:
function SuperType(){
this.property = true;
}
SuperType.prototype.getSuperValue = function(){
return this.property;
};
function SubType(){
this.subproperty = false;
}
//inherit from SuperType
SubType.prototype = new SuperType();
SubType.prototype.getSubValue = function (){
return this.subproperty;
};
var instance = new SubType();
alert(instance.getSuperValue()); //true
这里改变了SubType的原型,使其新原型为SuperType的实例。所以新原型不仅拥有SuperType全部的属性和方法,并且还有一个内部指针,指向SuperType的原型。这样就形成了继承的关系。
确定实例是否属于某构造函数
- 使用instanceof
alert(instance instanceof Object); //true
alert(instance instanceof SuperType); //true
alert(instance instanceof SubType); //true
其实instanceof 原理在于比较了实例的__proto__
与构造函数的prototype是否是同一引用。
不足:原型链上的所有构造函数都会被看做实例对象的构造函数,instanceof都返回true。所以用instanceof来判断,不能说某个对象一定是某个构造函数的实例,有可能是原型链上的其它函数。
解决办法:使用实例的__proto__.constructor
与构造函数名做比较更严谨,可以准确得出实例对象的构造函数。
instance.__proto__.constructor===Subtype //true
instance.__proto__.constructor===SuperType//false
instance.__proto__.constructor===Object//false
- 使用isPrototypeOf()
alert(Object.prototype.isPrototypeOf(instance)); //true
alert(SuperType.prototype.isPrototypeOf(instance)); //true
alert(SubType.prototype.isPrototypeOf(instance)); //true
谨慎定义方法
子类型有时需要重写超类型的某个方法或者添加超类型中不存在的某个方法。给原型添加方法的代码一定要放在替换原型的语句之后。
在通过原型链实现继承时,不能使用对象字面量创建原型方法,否则会切断原型链。
原型链的问题
其最大的问题在于对于引用类型值的继承,所有实例都拥有一个引用类型的指针,如果一个实例进行改写这个引用类型值,则这个改动会即刻应用到所有实例中。
还有就是在创建子类型的实例时,不能向超类型的构造函数中传递参数。
构造函数继承
这种继承方法的核心在于在子类型构造函数的内部调用超类型构造函数。
function SuperType(){
this.colors = ["red", "blue", "green"];
}
function SubType(){
//继承了SuperType的方法和属性
SuperType.call(this);
}
var instance1 = new SubType();
instance1.colors.push("black");
alert(instance1.colors); //"red,blue,green,black"
var instance2 = new SubType();
alert(instance2.colors); //"red,blue,green"
SuperType.call(this)
这一步主要是在新创建的SubType实例的环境下调用了SuperType构造函数,就会在新SubType对象上执行SuperType()函数中定义的所有对象初始化代码,SubType的每个实例都会具有自己属性代码。
组合继承
结合原型继承和构造函数继承,既能通过在原型上定义方法实现函数复用,又能保证每个实例都有自己的属性。
function SuperType(name){
this.name = name;
this.colors = ["red", "blue", "green"];
}
SuperType.prototype.sayName = function(){
alert(this.name);
};
function SubType(name, age){
SuperType.call(this, name);
this.age = age;
}
SubType.prototype = new SuperType();
SubType.prototype.sayAge = function(){
alert(this.age);
};
var instance1 = new SubType("Nicholas", 29);
instance1.colors.push("black");
alert(instance1.colors); //"red,blue,green,black"
instance1.sayName(); //"Nicholas";
instance1.sayAge(); //29
var instance2 = new SubType("Greg", 27);
alert(instance2.colors); //"red,blue,green"
instance2.sayName(); //"Greg";
instance2.sayAge(); //27
将属性定义在SuperType构造函数中,并且支持传参,将方法定义在SuperType原型中,然后再SubType构造函数中调用SuperType构造函数。
组合继承最大的缺点在于,调用了两次超类型构造函数
寄生组合式继承
function object(o){
function F(){}
F.prototype = o;
return new F();
}
//Object.create的实现方式如上
function inheritPrototype(subType, superType){
var prototype = Object.create(superType.prototype); //create object
prototype.constructor = subType; //augment object
subType.prototype = prototype; //assign object
}
function SuperType(name){
this.name = name;
this.colors = ["red", "blue", "green"];
}
SuperType.prototype.sayName = function(){
console.log(this.name);
};
function SubType(name, age){
SuperType.call(this, name);
this.age = age;
}
inheritPrototype(SubType, SuperType);
SubType.prototype.sayAge = function(){
console.log(this.age);
};
var instance1 = new SubType("Nicholas", 29);
instance1.colors.push("black");
console.log(instance1.colors); //"red,blue,green,black"
instance1.sayName(); //"Nicholas";
instance1.sayAge(); //29
var instance2 = new SubType("Greg", 27);
console.log(instance2.colors); //"red,blue,green"
instance2.sayName(); //"Greg";
instance2.sayAge(); //27