先回顾一下上一讲的原型。
// 原型
function Person(name) {
this.name = name;
}
Person.prototype.sayName = function() {
alert(this.name)
}
var p1 = new Person('嘿嘿');
console.log(p1)
结果如下图:
每个构造函数都有一个原型对象,原型对象包含一个指向构造函数的指针(constructor),而实例都包含一个指向原型对象的内部指针(__proto __)。
构造函数Person中定义的属性(即实例属性)被挂在到实例p1上,原型方法被挂载到原型对象上。
原型链继承
思想:让原型对象等于另一个类型的实例。
function SuperType() {
this.property = true;
}
SuperType.prototype.getSuperValue = function() {
return this.property;
};
function SubType() {
this.subproperty = false;
}
SubType.prototype = new SuperType(); // 实现继承;也就是让SubType.prototype.constructor指向构造函数SuperType
console.log(SubType.prototype.constructor)
SubType.prototype.getSubValue = function() {
return this.subproperty;
}
var instance = new SubType();
alert(instance.getSuperValue()); // true
默认的原型
所有函数的默认原型都是Object实例,因此默认原型都会包含一个内部指针,指向Object.prototype。这也是所有自定义类型都会继承toString()和valueOf()等默认方法的根本原因。
添加方法
给原型添加方法的代码一定要放在替换原型的语句之后。
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;
}
// 覆盖SuperType中的方法
SubType.prototype.getSuperValue = function() {
return false
}
var instance = new SubType();
alert(instance.getSuperValue()); // false
通过原型链实现继承的时候,不能使用对象字面量创建原型方法。
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();
alert(instance.getSuperValue()); // error
使用字面量添加方法,使得现在的原型包含的是一个Object实例,而非SuperType实例。所以SubType和SuperType之间已经没有联系了。
原型链的问题
原先实例的属性变成了现在的原型属性。
function SuperType() {
this.colors = ['red', 'blue', 'green'];
// 相当于 this.colors = new Array('red', 'blue', 'green')
}
function SubType() {}
SubType.prototype = new SuperType();
var instance1 = new SubType();
console.log(instance1.colors); // ["red", "blue", "green"]
instance1.colors.push('yellow');
console.log(instance1.colors); // ["red", "blue", "green", "yellow"]
var instance2 = new SubType();
console.log(instance2.colors) // ["red", "blue", "green", "yellow"]
引用类型的实例属性要被放在构造函数中,因为每个引用类型值在实例上会重新创建一遍,创建后的引用类型值的属性或方法是不一样的。这里,让SubType.prototype等于SuperType的实例,也就让SuperType中的实例方法变成了原型方法, 被挂在到SubType.prototype上。
原型链实现继承还有一个问题:在创建子类型(SubType)的实例时,无法向超类型(SuperType)的构造函数中传递参数。
借用构造函数继承
在子类型构造函数中调用超类型构造函数。使用call或apply方法改变超类型构造函数this指向。
function SuperType(name) {
this.colors = ['red', 'blue', 'green'];
this.name = name;
}
function SubType() {
SuperType.call(this, '呵呵')
}
var instance1 = new SubType();
console.log(instance1.colors); // ["red", "blue", "green"]
instance1.colors.push('yellow');
console.log(instance1.colors); // ["red", "blue", "green", "yellow"]
console.log(instance1.name); // 呵呵
var instance2 = new SubType();
console.log(instance2.colors) // ["red", "blue", "green"]
借用构造函数的问题:在超类型原型中定义的方法,无法被子类型继承。
function A() {
this.name = '嘿嘿'
}
A.prototype.sayName = function() {
alert(this.name)
}
function B() {
A.call(this)
}
var b = new B();
b.sayName(); // error
组合继承(最常用方法,掌握这个就行了)
使用原型链实现对原型属性和方法的继承,使用构造函数实现对实例属性的继承。
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.constructor = SubType; // 为了修复SubType.prototype的结构
SubType.prototype.sayAge = function(){
alert(this.age);
}
var instance1 = new SubType('小红', 18);
instance1.colors.push('yellow');
console.log(instance1.colors);
instance1.sayName();
instance1.sayAge();
var instance2 = new SubType('老红', 80);
console.log(instance2.colors);
instance2.sayName();
instance2.sayAge();
原型式继承
原型式继承就是基于已有的对象创建新对象.
function object(o) {
function F() {}
F.prototype = o; // 使一个类型的原型对象等于另一个对象
return new F();
}
var person = {
name: '小芳',
friends: ['小明', '小华']
}
var anotherPerson = object(person);
anotherPerson.name = '老方';
anotherPerson.friends.push('老王');
var yetAnotherPerson = object(person);
yetAnotherPerson.name = '老明';
yetAnotherPerson.friends.push('二狗子');
console.log(person.friends) // ["小明", "小华", "老王", "二狗子"]
ES5为了规范原型式继承,新增Object.create()方法。Object.create()可接收两个参数:第一个参数是超类型的对象,第二个参数可选,为新对象添加属性。
var person = {
name: '小芳',
friends: ['小明', '小华']
}
var anotherPerson = Object.create(person);
anotherPerson.name = '老方';
anotherPerson.friends.push('老王');
var yetAnotherPerson = Object.create(person);
yetAnotherPerson.name = '老明';
yetAnotherPerson.friends.push('二狗子');
console.log(person.friends) // ["小明", "小华", "老王", "二狗子"]
var person = {
name: '小芳',
friends: ['小明', '小华']
}
var anotherPerson = Object.create(person, {
name: {
value: '老方'
}
})
console.log(anotherPerson.name) // '老方'
寄生式继承
function CreateAnother(original) {
var clone = Object(original);
clone.sayHi = function() {
alert('hi');
}
return clone
}
var person = {
name: '小芳',
friends: ['小明', '小华']
}
var anotherPerson = CreateAnother(person);
anotherPerson.sayHi();
anotherPerson.friends.push('老王');
console.log(person.friends) // ["小明", "小华", "老王"]
寄生组合式继承(最好的方法)
function inheritPrototype(subType, superType) {
var prototype = Object(superType.prototype);
prototype.constructor = subType; // 修正subType.prototype的constructor
subType.prototype = prototype;
}
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;
}
inheritPrototype(SubType, SuperType);
SubType.prototype.sayAge = function() {
alert(this.age)
}
var p1 = new SubType()
console.log(p1.colors) // ['red', 'blue', 'green']
注:以上内容总结自《javascript高级程序设计》