作者:李旭光
引用请标明出处
继承的操作需要有一个父类,这里使用构造函数外加原型来创建一个:
有下面两个类,下面实现 Child 继承 Father:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
|
function Father() { this.type = 'prople'; }
Father.prototype.eat = function() { console.log('吃东西啦'); };
function Child(name) { this.name = name; this.color = 'black'; }
|
原型继承(认贼作父)
关键点:子类原型等于父类的实例 Child.prototype = new Person()
(将父类指向子类的原型)。
1 2
| Child.prototype = new Father();
|
特点:
实例可继承的属性有:
- 实例的构造函数的属性
- 父类构造函数的属性
- 父类原型上的属性
新实例不会继承父类实例的属性
缺点:
- 新实例无法向父类构造函数传参
- 继承单一
- 所有新实例都会共享父类实例的属性。— 原型上的属性是共享的,一个实例修改了原型属性,另一个实例的原型属性也会被修改
构造继承(借腹生子)
在子类构造函数中调用父类构造函数
1 2 3 4
| function Child(name) { Father.call(this); }
|
关键点:用 call 或 apply 将父类构造函数引入子类函数(在子类函数中做了父类函数的自执行(复制))Person.call(this, ‘reng’)
特点:
- 只继承了父类构造函数的属性,没有继承父类原型的属性
- 解决了原型链继承的注意事项(缺点)1,2,3
- 可以继承多个构造函数的属性(call 可以多个)
- 在子实例中可以向父实例传参
缺点:
- 只能继承父类构造函数的属性
- 无法实现构造函数的复用。(每次用每次都要重新调用)
- 每个新实例都有构造函数的副本,臃肿
(不能继承父类原型,函数在构造函数中,每个子类实例不能共享函数,浪费内存。)
组合继承(原型继承+构造继承)
使用构造继承继承父类参数,使用原型继承继承父类函数
1 2 3 4 5 6 7
| function Child(name) { Father.call(this); }
Child.prototype = Father.prototype;
|
关键点:结合了两种模式的优点–向父类传参(call)和复用(prototype)
特点:
- 可以继承父类原型上的属性,可以传参,可复用
- 每个新实例引入的构造函数属性是私有的
缺点:
- 父类原型和子类原型是同一个对象,无法区分子类真正是由谁构造。
- 调用了两次父类的构造函数(耗内存)
- 子类的构造函数会代替原型上的那个父类构造函数(call 相当于拿到了父类构造函数的副本)
原型式继承(复制降级)
1 2 3 4 5 6 7 8 9 10 11
| function create(obj) { function F() {} F.prototype = obj; return new F(); } var father = new Father(); var child = create(father); console.log(child instanceof Father); console.log(child.job);
|
关键点:用一个函数包装一个对象,然后返回这个函数的调用,这个函数就变成了可以随意增添属性的实例或对象。Object.create()就是这个原理。
特点:
注意事项:
- 所有的实例都会继承原型上的属性
- 无法实现复用。(新实例属性都是后面添加的)
Object.create()
方法规范了原型式继承。这个方法接收两个参数,一个用作新对象原型的对象和(可选的)一个为新对象定义额外属性的对象。
1 2 3 4 5 6 7 8 9 10 11
| var child = Object.create(new Father()); console.log(child.job); console.log(child instanceof Father);
var child = Object.create(new Father(), { name: { value: 'come on' } }); child.sayHello();
|
寄生组合继承
它跟组合继承
一样,都比较常用。
寄生:在函数内返回对象然后调用
组合:
- 函数的原型等于另一个实例
- 在函数中用 apply 或 call 引入另一个构造函数,可传参
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
| function create(obj) { function F() {} F.prototype = obj; return new F(); }
var obj = create(Father.prototype);
function Child() { this.age = 100; Father.call(this); }
Child.prototype = obj;
console.log(Child.prototype.constructor); obj.constructor = Child; console.log(Child.prototype.constructor); var child = new Child();
console.log(child.job); console.log(child instanceof Father);
|
重点:修复了组合继承的问题
在上面的问题中,你可能发现了这么一个注释obj.constructor = Sub; // 一定要修复实例。
为什么要修正子类的构造函数的指向呢?
因为在不修正这个指向的时候,在获取构造函数返回的时候,在调用同名属性或方法取值上可能造成混乱。比如下面:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| function Car() {} Car.prototype.orderOneLikeThis = function() { return new this.constructor(); }; Car.prototype.advertise = function() { console.log('I am a generic car.'); };
function BMW() {} BMW.prototype = Object.create(Car.prototype); BMW.prototype.constructor = BMW; BMW.prototype.advertise = function() { console.log('I am BMW with lots of uber features.'); };
var x5 = new BMW();
var myNewToy = x5.orderOneLikeThis();
myNewToy.advertise();
|
object.create
在组合继承的基础上,子类继承一个由父类原型生成的空对象。
1 2 3 4 5 6 7 8 9 10
| function Child(name) { Father.call(this); }
Child.prototype = Object.create(Father.prototype, { constructor: { value: Child } });
|
inherits 函数 — Nodejs util.inherits 函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
|
function inherits = function(ctor, superCtor) { ctor.super_ = superCtor; ctor.prototype = Object.create(superCtor.prototype, { constructor: { value: ctor, enumerable: false, writable: true, configurable: true } }); };
function Child() { Father.call(this); } inherits(Child, Father);
Child.prototype.fun = ...
|