目录
JS作为面向对象的弱类型语言,继承也是其非常强大的特性之一。那么如何在JS中实现继承呢?
JS继承的实现方式
既然要实现继承,那么首先我们得有一个父类,代码如下:
// 定义一个动物类
function Animal (name) {
// 属性
this.name = name || 'Animal';
// 实例方法
this.sleep = function(){
console.log(this.name + '正在睡觉!');
}
}
// 原型方法
Animal.prototype.eat = function(food) {
console.log(this.name + '正在吃:' + food);
};
1. 原型链继承
核心: 将父类的实例作为子类的原型
function Cat(){}
Cat.prototype = new Animal();
Cat.prototype.name = 'cat';
// Test Code
var cat = new Cat();
console.log(cat.name); //cat
console.log(cat.eat('fish')); //cat正在吃:fish
console.log(cat.sleep()); //cat正在睡觉
console.log(cat instanceof Animal); //true
console.log(cat instanceof Cat); //true
特点:
- 非常纯粹的继承关系,实例是子类的实例,也是父类的实例
- 父类新增原型方法/原型属性,子类都能访问到
- 简单,易于实现
缺点:
- 可以在Cat构造函数中,为Cat实例增加实例属性。如果要新增原型属性和方法,则必须放在
new Animal()
这样的语句之后执行。- 无法实现多继承
- 来自原型对象的引用属性是所有实例共享的
- 创建子类实例时,无法向父类构造函数传参
原型链的问题
1. 包含引用类型值得原型
function SuperType() {
this.colors = ["red", "blue", "green"];
}
function SubType() {}
//继承了SuperType
SubType.prototype = new SuperType();
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,black
这个例子中的 SuperType 构造函数定义了一个 colors 属性,该属性包含一个数组(引用类型值)。SuperType 的每个实例都会有各自包含自己数组的 colors 属性。当 SubType 通过原型链继承了SuperType 之后,SubType.prototype 就变成了 SuperType 的一个实例,因此它也拥有了一个它自己的 colors 属性一一就跟专门创建了一个 SubType.prototype.colors 属性一样。结果是,SubType的所有实例都会共享这一个colors属性。因此,当我们修改instance1.colors时,instance2.colors的值也会跟着修改。
2.在创建子类型的实例时,不能向超类型的构造函数中传递参数。
推荐指数:★★(3、4两大致命缺陷)
2. 借用构造函数
核心:使用父类的构造函数来增强子类实例,等于是复制父类的实例属性给子类(没用到原型)
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
特点:
- 解决了1中,子类实例共享父类引用属性的问题
- 创建子类实例时,可以向父类传递参数
- 可以实现多继承(call多个父类对象)
缺点:
- 实例并不是父类的实例,只是子类的实例
- 只能继承父类的实例属性和方法,不能继承原型属性/方法
- 无法实现函数复用,每个子类都有父类实例函数的副本,影响性能
推荐指数:★★(缺点3)
3. 组合继承(常用)
将原型链和借用构造函数的技术组合到一块 ,从而发挥二者之长的一种继承模式。其背后的思路是使用原型链实现对原型属性和方法的继承,而通过借用构造函数来实现对实例属性的继承。这样,既通过在原型上定义方法实现了函数复用,又能够保证每个实例都有它自己的属性。
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;
}
//通过原型链继承了SuperType的方法
SubType.prototype = new SuperType();
//添加原型方法
SubType.prototype.sayAge = function() {
alert(this.age);
};
var instance1 = new SubType("Nike", 28);
instance1.colors.push("black");
alert(instance1.colors); //red,blue,green,black
instance1.sayName(); //Nike
instance1.sayAge(); //28
var instance2 = new SubType("Cindy", 34);
alert(instance2.colors); //red,blue,green
instance2.sayName(); //Cindy
instance2.sayAge(); //34
在这个例子中,SuperType 构造函数定义了两个属性:name 和 colors。SuperType的原型定义了一个方法 sayName()。SubType 构造函数在调用SuperType 构造函数时传入了 name 参数,紧接着又定义了它自己的属性 age。然后,将 SuperType 的实例赋值给 SubType 的原型.然后又在该新原型上定义了方法sayAge() 。这样一来 ,就可以让两个不同的 SubType 实例既分别拥有自己属性一一包 括 colors 属性,又可以使用相同的方法了。
特点:
- 弥补了方式2的缺陷,可以继承实例属性/方法,也可以继承原型属性/方法
- 既是子类的实例,也是父类的实例
- 不存在引用属性共享问题
- 可传参
- 函数可复用
缺点:
- 调用了两次父类构造函数,生成了两份实例(子类实例将子类原型上的那份屏蔽了)
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); //第二次调用SuperType
this.age = age;
}
//通过原型链继承了SuperType的方法
SubType.prototype = new SuperType(); //第一次调用SuperType
SubType.prototype.constructor = SubType;
SubType.prototype.sayAge = function() {
alert(this.age);
};
在第一次调用 SuperType 构造函数时, SubType.prototype 会得到两个属性 :name 和 colors;它们都是 SuperType 的实例属性,只不过现在位于 SubType 的原型中。当调用SubType构造函数时,又会调用一次 SuperType 构造函数,这一次又在新对象上创建了实例属性 name 和 colors。于是,这两个属性就屏蔽了原型中的两个同名属性。
解决:寄生组合式继承
推荐指数:★★★★(仅仅多消耗了一点内存)
4. 原型式继承
借助原型可以基于已有的对象创建新对象。
由来:
在 object()函数内部,先创建了一个临时性的构造函数,然后将传入的对象作为这个构造函数的原型,最后返回了这个临时类型的一个新实例。从本质上讲,object()对传入其中的对象执行了一次浅复制。
function object(o) {
function F() {}
F.prototype = o;
return new F();
}
var person = {
name: "Nike",
friends: ["Shelby", "Court", "Van"]
};
var anotherPerson = object(person);
anotherPerson.name = "Greg";
anotherPerson.friends.push("Rob");
var YetAnotherPerson = object(person);
YetAnotherPerson.name = "Linda";
YetAnotherPerson.friends.push("Sarbie");
alert(person.friends); //Shelby,Court,Van,Rob,Sarbie
在这个例子中,可以作为另一个对象基础的是 person 对象,于是我们把它传入到 object()函数中,然后该函数就会返回一个新对象。这个新对象将 person 作为原型,所以它的原型中就包含一个基本类型值属性和一个引用类型值属性,这意味person.friends 不仅属于 person 所有,而且也会被 anotherPerson 以及 yetAnotherPerson 共享。实际上,这就相当于又创建了person 对象的两个副本。
5. 寄生式继承
创建一个仅用于封装继承过程的函数,该函数在内部以某种方式来增强对象,最后再像真地是它做了所有工作一样返回对象。createAnother()函数接收了一个参数 ,也就是将要作为新对象基础的对象。然 后,把这个对象传递给 object()函数,将返回的结果赋值给 clone。再为 clone 对象添加一个新方法 sayHi(),最后返回 clone 对象。
function object(o) {
function F() {}
F.prototype = o;
return new F();
}
function createAnother(original) {
var clone = object(original); //通过调用函数创建一个新对象
clone.sayHi = function() { //以某种方式来增强这个对象
alert("hi");
};
return clone; //返回这个对象
}
var person = {
name: "Nike",
friends: ["Shelby", "Court", "Van"]
};
var anotherPerson = createAnother(person);
anotherPerson.sayHi(); //hi
这个例子中的代码基于 person 返回了一个新对象一 anotherPerson。新对象不仅具有 person的所有属性和方法,而且还有自己的sayHi()方法。
6. 寄生组合继承
即通过借用构造函数来继承属性,通过原型链的混成形式来继承方法。其背后的基本思路是:不必为了指定子类型的原型而调用超类型的构造函数,我们所需要的无非就是超类型原型的一个副本而已。本质上,就是使用寄生式继承来继承超类型的原型,然后再将结果指定给子类型。
function object(o) {
function F() {}
F.prototype = o;
return new F();
};
function inheritPrototype(subType, superType) {
var prototype = object(superType.prototype); //创建对象
prototype.constructor = subType; //增强对象
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 instance1 = new SubType("Nike", 28);
instance1.colors.push("black");
alert(instance1.colors); //red,blue,green,black
instance1.sayName(); //Nike
instance1.sayAge(); //28
var instance2 = new SubType("Cindy", 34);
alert(instance2.colors); //red,blue,green
instance2.sayName(); //Cindy
instance2.sayAge(); //34
特点:
- 堪称完美
缺点:
- 实现较为复杂
推荐指数:★★★★(实现复杂,扣掉一颗星)