原型链继承
[JavaScript]
纯文本查看
复制代码
01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 | function Vehicle(powerSource) { this.powerSource = powerSource; this.components = ['座椅', '轮子'];}Vehicle.prototype.run = function() { console.log('running~');};function Car(wheelNumber) { this.wheelNumber = wheelNumber;}Car.prototype.playMusic = function() { console.log('sing~');};// 将父构造函数的实例赋值给子构造函数的原型Car.prototype = new Vehicle();const car1 = new Car(4); |
上面这个例子中,首先定义一个叫做 交通工具 的构造函数,它有两个属性分别是是 驱动方式 和 组成部分,还有一个原型方法是 跑;接下来定义叫做 汽车 的构造函数,它有 轮胎数量 属性和 播放音乐 方法。我们将 Vehicle 的实例赋值给 Car 的原型,并创建一个名叫 car1 的实例
但是该方式有几个缺点:
- 多个实例对引用类型的操作会被篡改
- 子类型的原型上的 constructor 属性被重写了
- 给子类型原型添加属性和方法必须在替换原型之后
- 创建子类型实例时无法向父类型的构造函数传参
借用构造函数继承
[JavaScript]
纯文本查看
复制代码
1 | //该方法又叫 伪造对象 或 经典继承。它的实质是 在创建子类实例时调用父类的构造函数。 |
[JavaScript]
纯文本查看
复制代码
01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 | function Vehicle(powerSource) { this.powerSource = powerSource; this.components = ['座椅', '轮子'];}Vehicle.prototype.run = function() { console.log('running~');};function Car(wheelNumber) { this.wheelNumber = wheelNumber; // 继承父类属性并且可以传参 Vehicle.call(this, '汽油');}Car.prototype.playMusic = function() { console.log('sing~');};const car = new Car(4); |
使用经典继承的好处是可以给父类传参,并且该方法不会重写子类的原型,故也不会损坏子类的原型方法。此外,由于每个实例都会将父类中的属性复制一份,所以也不会发生多个实例篡改引用类型的问题(因为父类的实例属性不在原型中了)。
然而缺点也是显而易见的,我们丝毫找不到 run 方法的影子,这是因为该方式只能继承父类的实例属性和方法,不能继承原型上的属性和方法。
回忆上一篇文章讲到的构造函数,为了将公有方法放到所有实例都能访问到的地方,我们一般将它们放到构造函数的原型中。而如果让 借用构造函数继承 运作下去,显然需要将 公有方法 写在构造函数里而非其原型,这在创建多个实例时势必造成浪费。
组合继承
组合继承吸收上面两种方式的优点,它使用原型链实现对原型方法的继承,并借用构造函数来实现对实例属性的继承。
[JavaScript]
纯文本查看
复制代码
01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | function Vehicle(powerSource) { this.powerSource = powerSource; this.components = ['座椅', '轮子'];}Vehicle.prototype.run = function() { console.log('running~');};function Car(wheelNumber) { this.wheelNumber = wheelNumber; Vehicle.call(this, '汽油'); // 第二次调用父类}Car.prototype = new Vehicle(); // 第一次调用父类// 修正构造函数的指向Car.prototype.constructor = Car;Car.prototype.playMusic = function() { console.log('sing~');};const car = new Car(4); |
原型式继承
该方式通过借助原型,基于已有对象创建新的对象。
首先创建一个名为 object 的函数,然后在里面中创建一个空的函数 F,并将该函数的 prototype 指向传入的对象,最后返回该函数的实例。本质来讲,object() 对传入的对象做了一次 浅拷贝。
[JavaScript]
纯文本查看
复制代码
01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 | function object(proto) { function F() {} F.prototype = proto; return new F();}const cat = { name: 'Lolita', friends: ['Yancey', 'Sayaka', 'Mitsuha'], say() { console.log(this.name); },};const cat1 = object(cat); |
虽然这种方式很简洁,但仍然有一些问题。因为 原型式继承 相当于 浅拷贝,所以会导致 引用类型 被多个实例篡改。下面这个例子中,我们给 cat1.friends 追加一个元素,却导致 cat.friends 被篡改了。
寄生式继承
[JavaScript]
纯文本查看
复制代码
01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 22 | const cat = { name: 'Lolita', friends: ['Yancey', 'Sayaka', 'Mitsuha'], say() { console.log(this.name); },};function createAnother(original) { const clone = Object.create(original); // 获取源对象的副本 clone.gender = 'female'; clone.fly = function() { // 增强这个对象 console.log('I can fly.'); }; return clone; // 返回这个对象}const cat1 = createAnother(cat); |
和
原型式继承
一样,该方式会导致
引用类型
被多个实例篡改,此外,
fly
方法存在于
实例
而非
原型
中,因此
函数复用
无从谈起。
寄生组合式继承
[JavaScript]
纯文本查看
复制代码
01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 | function Vehicle(powerSource) { this.powerSource = powerSource; this.components = ['座椅', '轮子'];}Vehicle.prototype.run = function() { console.log('running~');};function Car(wheelNumber) { this.wheelNumber = wheelNumber; Vehicle.call(this, '汽油');}inheritPrototype(Car, Vehicle);Car.prototype.playMusic = function() { console.log('sing~');}; |
看上面这张图就知道为什么这是最好的方法了。它只调用了一次父类,因此避免了在子类的原型上创建多余的属性,并且原型链结构还能保持不变。
硬要说缺点的话,给子类型原型添加属性和方法仍要放在 inheritPrototype 函数之后。
https://juejin.cn/post/6844903816798666760?utm_source=gold_browser_extension