可以利用 JavaScript 原型,让一个引用类型继承另一个引用类型的属性和方法:
//父类
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;
};
var instance = new SubType();
console.log(instance.getSuperValue());//true
实现的本质是重写原型对象,代之以一个新类型的实例。它们之间的关系:
在通过原型链实现继承的情况下,搜索会沿着原型链向上查找,直到原型链末端才会停止,上面例子的搜索顺序是:
1. 搜索实例。
2. 搜索 SubType.prototype。
3. 搜索 SuperType.prototype。
注意,所有的引用类型默认都是继承自 Object,而这个继承也是通过原型链来实现的。因此,上面的例子的完整关系图是:
如果实例的类型在原型链中曾经出现过,那么使用 instanceof 操作符就会返回 true:
console.log(instance instanceof Object);//true
console.log(instance instanceof SuperType);//true
console.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
子类型有时候需要重新父类型的方法,或者添加父类型中不存在的方法,记住,给原型添加方法一定要放在替换原型的语句后:
//父类
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;
};
//重写父类中的方法
SubType.prototype.getSuperValue= function () {
return false;
};
var instance = new SubType();
console.log(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();
console.log(instance.getSuperValue());//错误
包含引用类型值的原型中定义的属性会被所有实例所共享,而通过原型链来实现继承时,原型变成另一个类型的实例,所以原先的实例中定义的属性也就变成了现在的原型属性了:
function SuperType() {
this.colors = ["red", "blue", "green"];
}
function SubType() {
}
//继承父类型
SubType.prototype = new SuperType();
var instance1 = new SubType();
instance1.colors.push("black");
console.log(instance1.colors);//"red","blue","green","black"
var instance2 = new SubType();
console.log(instance2.colors);//"red","blue","green","black"
第二个问题是:在创建子类型的实例时,不能向父类型的构造函数传递参数。
因为这些问题,所以在实践中很少单独使用原型链来实现继承。
因为函数是在特定环境下执行代码的对象,因此可以通过 apply() 或 call() 方法在新创建的对象上执行构造函数,即在子类型构造函数的内部调用父类型的构造函数,这样每个子类型实例就会有专属于它们自己的父类型属性了:
function SuperType(){
this.colors=["red","blue","green"];
}
function SubType(){
//继承父类型
SuperType.call(this);
}
var instance1=new SubType();
instance1.colors.push("black");
console.log(instance1.colors);//"red","blue","green","black"
var instance2=new SubType();
console.log(instance2.colors);//"red","blue","green"
借用构造函数方式最大的优点就是,可以在子类型的构造函数中向超类型构造函数传递参数:
function SuperType(name) {
this.name = name;
}
function SubType() {
//继承父类的同时还传递了参数
SuperType.call(this, "deniro");
//实例属性
this.age = 29;
}
var instance = new SubType();
console.log(instance.name);//deniro
console.log(instance.age);//29
它的问题就像之前说到的构造函数模式创建对象一样,方法都只能在构造函数中定义,就无法实现复用函数,因此借用构造函数模式也很少单独使用。
使用原型链实现对原型属性和方法的继承,然后借用构造函数实现对实例属性的继承:
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;
}
//继承父类型的方法
SubType.prototype = new SuperType();
//定义自己的方法
SubType.prototype.sayAge = function () {
console.log(this.age);
};
var instance1 = new SubType("Deniro", 19);
instance1.colors.push("black");
console.log(instance1.colors);//"red","blue","green","black"
instance1.sayName();//Deniro
instance1.sayAge();//19
var instance2 = new SubType("Lily", 15);
console.log(instance2.colors);//"red","blue","green"
instance2.sayName();//Lily
instance2.sayAge();//15
组合继承方式融合了原型链和借用构造函数方式的优点,而且 instanceof 和 isPrototypeOf() 也能够识别组合继承方式创建的对象,因此是最常用的继承模式。
通过借助原型在基于已有对象上创建新的对象,这样还不必创建自定义类型。首先需要定义了这样一个函数:
function object(o) {
//创建临时性的构造函数
function F() {
}
F.prototype = o;//将传入的对象作为这个构造函数的原型
return new F();//返回这个临时类型的一个新实例
}
var person = {
name: "Deniro",
friends: ["Lily", "Jack", "Bruce"]
};
var anotherPerson = object(person);
anotherPerson.name = "Nancy";
anotherPerson.friends.push("Rob");
var yetAnotherPerson = object(person);
yetAnotherPerson.name = "Linda";
yetAnotherPerson.friends.push("Barbie");
console.log(person.friends);//"Lily","Jack","Bruce","Rob","Barbie"
ECMAScript 5 使用 Object.create() 规范化了原型式继承。
输入参数 | 是否必填 |
---|---|
用作新对象的原型对象 | 是 |
为新对象定义额外属性的对象 | 否 |
在传入一个参数的情况下,Object.create() 与 之前定义的 object() 行为相同。
var person = {
name: "Deniro",
friends: ["Lily", "Jack", "Bruce"]
};
var anotherPerson = Object.create(person);
anotherPerson.name = "Nancy";
anotherPerson.friends.push("Rob");
var yetAnotherPerson = Object.create(person);
yetAnotherPerson.name = "Linda";
yetAnotherPerson.friends.push("Barbie");
console.log(person.friends);//"Lily","Jack","Bruce","Rob","Barbie"
Object.create() 的第二个参数与 Object.defineProperties() 方法的第二个参数格式相同:每个属性都是通过自己的描述符来定义的,这样定的的属性会覆盖原型对象上的同名属性:
var person = {
name: "Deniro",
friends: ["Lily", "Jack", "Bruce"]
};
var anotherPerson = Object.create(person, {
name: {
value: "Greg"
}
});
console.log(anotherPerson.name);//"Greg"
如果只想让一个对象与另一个对象保持相似,那么可以使用原型式继承。不过要记住,包含引用类型值的属性会被所有的子类型所共享。
创建一个只用于封装继承过程的函数,在函数内部以某种方法来增强对象,最后再返回这个对象:
function object(o) {
function F() {
}
F.prototype = o;
return new F();
}
function createAnother(original) {
var clone = object(original);//创建一个新对象
clone.sayHi = function () {//增强这个对象
console.log("hi");
};
return clone;//返回这个对象
}
var person = {
name: "Deniro",
friends: ["Lily", "Jack", "Bruce"]
};
var anotherPerson = createAnother(person);
anotherPerson.sayHi();//hi
在主要考虑对象的情况下,可以用这个模式。示例中的 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);//第二次调用 SuperType()
this.age = age;
}
//继承方法
SubType.prototype = new SuperType();//第一次调用 SuperType()
SubType.prototype.sayAge = function () {
console.log(this.age);
};
第一次调用 SuperType 构造函数时,SubType.prototype 会得到两个属性:name 和 colors。当调用 SubType 构造函数时,又会调用一次 SuperType 构造函数,这又会在新对象上创建 name 和 colors 的属性,这两个属性就屏蔽了原型中的两个同名属性。
寄生组合式继承,是通过借用构造函数来继承属性,通过原型链来继承方法。基本模式是:
function inheritPrototype(subType,superType){
var prototype = object(superType.prototype);//创建父类型原型的一个副本
prototype.constructor = subType;//增强对象,添加 constructor 属性
subType.prototype = prototype;//将这个副本赋值给子类型的原型
}
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);
};
这种方式比组合继承高效,因为它只调用一次 SuperType 构造函数,而且还避免了在 SubType.prototype 上创建不必要的、多余的属性。还能正常使用 instanceof 和 isPrototypeOf,是不是很棒O(∩_∩)O~。
综合以上优点,寄生组合式继承被认为是引用类型最理想的继承范式。