javascript 继承收集

定义父类

1
2
3
4
5
6
7
8
9
10
11
12
13
// 定义一个动物类
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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
// 继承
function inherit(superConstructor, subConstructor) {
var proto = beget(superConstructor.prototype); // 核心
proto.constructor = subConstructor; // 修改构造函数
subConstructor.prototype = proto; // 修改原型链,将实例作为子类的原型
};
function beget(obj) {
var F = function () {};
F.prototype = obj;
return new F();
}
function Cat(name) {
Animal.call(this, name); // 调用父类构造函数,创建属性和实例方法
this.base = this.__proto__.__proto__; // 定义基类对象
this.color = 'white';
// 重载实例方法,也可以使用原型方法重载
this.sleep = function(fav) {
if (fav) {
console.log(this.name + ' 最喜欢在' + fav + '睡觉');
} else {
this.base.sleep.call(this); // 注意这里是调用基类方法,使用 call 改变调用对象
}
}
}
inherit(Animal, Cat); // Cat 继承 Animal
// 重载原型方法,必须在继承后
Cat.prototype.eat = function(food, fav){
if (fav) {
console.log(this.name + ' 最喜欢的食物是:' + food);
} else {
this.base.eat.call(this, food); // 调用基类方法,使用 call 或 apply
// this.base.eat.apply(this, arguments);
}
}
var cat = new Cat('Tom cat');
cat.eat('fish', true); // cat 最喜欢的食物是:fish
cat.eat('fish'); // cat 正在吃:fish
console.log(typeof cat.sleep === 'function'); // true, 实例方法无法继承

继承可使用上述代码实现,如需详细了解其他方式可见下文。

========== 分界线 ===========

原型链继承

核心: 将父类的实例作为子类的原型

1
2
3
4
5
6
7
8
9
10
11
12
function Cat(){
}
Cat.prototype = new Animal();
Cat.prototype.name = 'cat';
// Test Code
var cat = new Cat();
console.log(cat.name);
console.log(cat.eat('fish'));
console.log(cat.sleep());
console.log(cat instanceof Animal); // true
console.log(cat instanceof Cat); // true

问题:

  • 要想为子类新增属性和方法,必须要在new Animal()这样的语句之后执行,不能放到构造器中
  • 无法实现多继承
  • 来自原型对象的引用属性是所有实例共享的
  • 创建子类实例时,无法向父类构造函数传参

构造继承

核心:使用父类的构造函数来增强子类实例,等于是复制父类的实例属性给子类(没用到原型)

1
2
3
4
5
6
7
8
9
10
11
function Cat(name){
Animal.call(this);
this.name = name || 'Tom';
}
// Test Code
var cat = new Cat();
console.log(cat.name);
console.log(cat.sleep());
console.log(cat instanceof Animal); // false
console.log(cat instanceof Cat); // true

问题

  • 实例并不是父类的实例,只是子类的实例
  • 只能继承父类的实例属性和方法,不能继承原型属性/方法
  • 无法实现函数复用,每个子类都有父类实例函数的副本,影响性能

实例继承

核心:为父类实例添加新特性,作为子类实例返回

1
2
3
4
5
6
7
8
9
10
11
12
function Cat(name){
var instance = new Animal();
instance.name = name || 'Tom';
return instance;
}
// Test Code
var cat = new Cat();
console.log(cat.name);
console.log(cat.sleep());
console.log(cat instanceof Animal); // true
console.log(cat instanceof Cat); // false

问题

  • 实例是父类的实例,不是子类的实例
  • 不支持多继承

拷贝继承

1
2
3
4
5
6
7
8
9
10
11
12
13
14
function Cat(name){
var animal = new Animal();
for(var p in animal){
Cat.prototype[p] = animal[p];
}
Cat.prototype.name = name || 'Tom';
}
// Test Code
var cat = new Cat();
console.log(cat.name);
console.log(cat.sleep());
console.log(cat instanceof Animal); // false
console.log(cat instanceof Cat); // true

问题

  • 效率较低,内存占用高(因为要拷贝父类的属性)
  • 无法获取父类不可枚举的方法(不可枚举方法,不能使用for in 访问到)

组合继承

核心:通过调用父类构造,继承父类的属性并保留传参的优点,然后通过将父类实例作为子类原型,实现函数复用

1
2
3
4
5
6
7
8
9
10
11
12
function Cat(name){
Animal.call(this);
this.name = name || 'Tom';
}
Cat.prototype = new Animal();
// Test Code
var cat = new Cat();
console.log(cat.name);
console.log(cat.sleep());
console.log(cat instanceof Animal); // true
console.log(cat instanceof Cat); // true

问题

  • 调用了两次父类构造函数,生成了两份实例(子类实例将子类原型上的那份屏蔽了)

寄生组合继承(实现复杂)

核心:通过寄生方式,砍掉父类的实例属性,这样,在调用两次父类的构造的时候,就不会初始化两次实例方法/属性,避免的组合继承的缺点

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
function Cat(name){
Animal.call(this);
this.name = name || 'Tom';
}
(function(){
// 创建一个没有实例方法的类
var Super = function(){};
Super.prototype = Animal.prototype;
//将实例作为子类的原型
Cat.prototype = new Super();
})();
// Test Code
var cat = new Cat();
console.log(cat.name);
console.log(cat.sleep());
console.log(cat instanceof Animal); // true
console.log(cat instanceof Cat); // true