构造函数、原型和实例之间的关系
关系图:
原型链的基本概念
基于上述关系图的理解,让原型对象等于另一个类型的实例,假如这个类型的原型又等于另一个类型的实例,这样层层递进,构成了实例和原型的链条。
关系图:
原型链的代码实现的基本模式
//组合构造函数模式和原型模式
function SuperType() {
this.property = true;
}
SuperType.prototype.getSuperValue = function() {
return this.property;
};
function SubType() {
this.subproperty = false;
}
//继承了SuperType
SubType.prototype = new SuperType();
SubType.prototype.getSubValue = function() {
return this.subproperty;
};
var instance = new SubType();
console.log(instance.getSuperValue()); //true
console.log(instance.constructor === SuperType); //true
console.log(SubType.prototype.constructor === SuperType); //true
SubType继承了SuperType。继承是通过创建SuperType实例,然后赋值给SubType.prototype实现的。实现的本质是重写原型对象,代之以一个新类型的实例。
代码关系图:
关系结果:instance指向SubType的原型,Sub-Type的原型又指向SuperType的原型。
注意两点:
- getSuperValue()方法仍然还在SuperType.prototype中,但property则位于Sub-Type.prototype中。这是因为property是一个实例属性,而get-SuperValue()则是一个原型方法。既然SubType.prototype现在是SuperType的实例,那么property当然就位于该实例中了。
- instance.constructor现在指向的是SuperType。原因:SubType 的原型指向了另一个对象——SuperType 的原型,而这个原型对象的constructor 属性指向的是SuperType。
通过实现原型链,本质上扩展了原型搜索机制。调用instance.getSu-perValue()会经历三个搜索步骤:
- 搜索实例;
- 搜索Sub-Type.prototype;
- 搜索SuperType.prototype,最后一步才会找到该方法。
原型链的最顶层
所有函数的默认原型都是Object的实例,因此默认原型都会包含一个内部指针,指向Object.prototype。
继承关系图(完整版):
SubType继承了SuperType,而SuperType了继承Object。当调用instance.toString()时,实际上调用的是保存在Object.prototype中的那个方法。
确定原型和实例的关系
instanceof操作符和isPrototypeOf()方法
//instanceof操作符
console.log(instance instanceof Object); //true
console.log(instance instanceof SuperType); //true
consolo.log(instance instanceof SubType); //true
//isPrototypeOf()方法
console.log(Object.prototype.isPrototypeOf(instance)); //true
console.log(SuperType.prototype.isPrototypeOf(instance)); //true
console.log(SubType.prototype.isPrototypeOf(instance)); //true
子类型定义方法注意
- 重写父类方法或定义新方法注意:给原型添加方法的代码一定要放在替换原型的语句之后。为什么?因为,继承的本质就是重写子类的prototype,如果写在继承前面,那么后面创建实例的时候就访问不到新添加或修改的方法了。
- 在通过原型链实现继承时,不能使用对象字面量创建原型方法。
function SuperType() {
this.property = true;
}
SuperType.prototype.getSuperValue = function() {
return this.property;
};
function SubType() {
this.subproperty = false;
}
//继承SuperType,这个在前
SubType.prototype = new SuperType();
//子类添加父类没有的方法,在后
SubType.prototype.getSubValue = function() {
return this.subproperty;
};
//重写父类方法,在后
SubType.prototype.getSuperValue = function() {
return false;
};
var instance = new SubType();
console.log(instance.getSuperValue()); //false
var instanceSuper = new SuperType();
console.log(instanceSuper.getSuperValue()); //true
由上述结果可以看出:子类重写父类方法,只是屏蔽,原方法不变。
function SuperType() {
this.property = true;
}
SuperType.prototype.getSuperValue = function() {
return this.property;
};
function SubType() {
this.subproperty = false;
}
//继承
SubType.prototype = new SuperType();
//字面量定义方法会使上一句代码无效
SubType.prototype = {
getSubValue: function() {
return this.subproperty;
},
someOtherMethod: function() {
return false;
}
};
var instance = new SubType();
console.log(instance.getSuperValue()); //error
上述继承后字面量写原型对象的方法会导致下面的错误是因为:现在的原型包含的是一个Object的实例,而非SuperType的实例,因此我们设想中的原型链已经被切断,现在的SubType和SuperType没关系了。
原型链问题
- 包含引用类型值的原型;
- 在创建子类型的实例时,不能向超类型的构造函数中传递参数。
//第一个问题
function SuperType() {
this.colors = ["blue", "red", "yellow"];
}
function SubType() {
}
//继承
SubType.prototype = new SuperType();
var instance1 = new SubType();
instance1.colors.push("black");
console.log(instance1.colors); //"blue,red,yellow,black"
var instance2 = new SubType();
console.log(instance2.colors); //"blue,red,yellow,black"
instance2的colors输出和instance1的colors一样,是因为这属性不是他们自己的,是SuperType实例的,不是他们本身的。