AWebManCN

Menu

做完这48道题彻底弄懂JS继承

哈哈哈哈。

既然这样的话,就来看看这系列的大头——继承

这篇文章的继承题可是有点东西的啊,基本覆盖了所有主流的继承情况,而且都比较细节,如果你原来只是浅浅的看了一些教材,跟着手写实现了一下而已的话,那你看完保证是会有收获的!那样的话还请给个三连哦 😊。

☑️点赞➕收藏➕关注

❌ 闪现➕大招➕引燃

老规矩,否则在评论区给我一个臭臭的👎。

全文共有1.7w字,前前后后整理了快两个星期(整理真的很容易掉头发😂)。

所以还请你找个安静的地方,在一个合适的时间来细细品味它 😊。

OK👌,废话不多说,咱走着,卡加(韩语)~

JS继承系列介绍

通过阅读本篇文章你可以学习到:

(在正式阅读本篇文章之前还请先查看封装篇,也就是目录的第一章节,之后观看舒适感更高哦 😁)

继承

好滴👌,还是让我们先来了解一下继承的概念哈。

继承 🤔️?

"嗯...我爸在深圳福田有一套房,以后要继承给我"

"啪!"

"我提莫的在想什么?我还有个弟弟,所以我爸得有两套"

"啪!"

"你提莫还在睡,该搬砖了!"

正经点的,其实一句话来说:

继承就是子类可以使用父类的所有功能,并且对这些功能进行扩展。

比如我有个构造函数A,然后又有个构造函数B,但是B想要使用A里的一些属性和方法,一种办法就是让我们自身化身为CV侠,复制粘贴一波。还有一种就是利用继承,我让B直接继承了A里的功能,这样我就能用它了。

今天要介绍的八种继承方式在目录中都已经列举出来了。

不着急,从浅到深咱一个个来看。

1. 原型链继承

将子类的原型对象指向父类的实例

1.1 题目一

(理解原型链继承的概念)

function Parent () {
  this.name = 'Parent'
  this.sex = 'boy'
}
Parent.prototype.getName = function () {
  console.log(this.name)
}
function Child () {
  this.name = 'child'
}
Child.prototype = new Parent()

var child1 = new Child()
child1.getName()
console.log(child1)
复制代码

好了,快告诉我答案吧,会打印出什么 🤔️ ?

'child'
Child {name: "child"}
复制代码

这...这很好理解呀

这看着不就是之前都讲到过的内容嘛?

就像是题目1.61.7一样(《封装篇(牛刀小试)》里的)。

所以现在你知道了吧,这种方式就叫做原型链继承

将子类的原型对象指向父类的实例。

我们来写个伪代码,方便记忆:

Child.prototype = new Parent()
复制代码

当然,更加严谨一点的做法其实还有一步:Child.prototype.constructor = Child,不过这边霖呆呆先卖个关子,到题目4.2中我们再来详细说它。

1.2 题目二

不知道你们在看到原型链继承这个词语的时候,第一时间想到的是什么?

有没有和我一样,想到的是把子类的原型对象指向父类的原型对象的😂:

Child.prototype = Parent.prototype
复制代码

和我一样的举个手给我看下🙋‍♂️,😂

之后我就为我xx似的想法感到惭愧...

如果我只能拿到父类原型链上的属性和方法那也太废了吧,我可不止这样,我还想拿到父类构造函数上的属性。

所以这道题:

function Parent () {
  this.name = 'Parent'
  this.sex = 'boy'
}
Parent.prototype.getSex = function () {
  console.log(this.sex)
}
function Child () {
  this.name = 'child'
}
Child.prototype = Parent.prototype

var child1 = new Child()
child1.getSex()
console.log(child1)
复制代码

结果为:

undefined
Child {name: "child"}
复制代码

你可以结合上面👆的那张图,自个儿脑补一下,child1它的原型链现在长啥样了。

解析:

这道题是个错误的做法啊 😂

我只是为了说明一下,为什么原型链继承是要用Child.prototype = new Parent()这种方式。

1.3 题目三

(理解原型链继承的优点和缺点)

这道题的结果大家能想到吗?

请注意对象是地址引用的哦。

function Parent (name) {
  this.name = name
  this.sex = 'boy'
  this.colors = ['white', 'black']
}
function Child () {
  this.feature = ['cute']
}
var parent = new Parent('parent')
Child.prototype = parent

var child1 = new Child('child1')
child1.sex = 'girl'
child1.colors.push('yellow')
child1.feature.push('sunshine')

var child2 = new Child('child2')

console.log(child1)
console.log(child2)

console.log(child1.name)
console.log(child2.colors)

console.log(parent)
复制代码

答案:

Child{ feature: ['cute', 'sunshine'], sex: 'girl' }
Child{ feature: ['cute'] }

'parent'
['white', 'black', 'yellow']

Parent {name: "parent", sex: 'boy', colors: ['white', 'black', 'yellow'] }
复制代码

解析:

分析的真漂亮,漂亮的这么一大串我都不想看了...

咳咳,不过你要是能静下来认真的读一读的话就会觉得真没啥东西,甚至不需要记什么,我就理解了。

总结-原型链继承

现在我们就可以得出原型链继承它的优点和缺点了

优点:

缺点:

这...这看到没,压根就不需要记,想想霖呆呆出的这道变态的题面试的时候被问到脱口就来了。

2. instanceof

2.1 题目一

这道题主要是想介绍一个重要的运算符: instanceof

先看看官方的简介:

instanceof 运算符用于检测构造函数的 prototype 属性是否出现在某个实例对象的原型链上。

再来看看通俗点的简介:

a instanceof B

实例对象a instanceof 构造函数B

检测a的原型链(__proto__)上是否有B.prototype,有则返回true,否则返回false

上题吧:

function Parent () {
  this.name = 'parent'
}
function Child () {
  this.sex = 'boy'
}
Child.prototype = new Parent()
var child1 = new Child()

console.log(child1 instanceof Child)
console.log(child1 instanceof Parent)
console.log(child1 instanceof Object)
复制代码

结果为:

true
true
true
复制代码

这里就利用了前面👆提到的原型链继承,而且三个构造函数的原型对象都存在于child1的原型链上。

也就是说,左边的child1它会向它的原型链中不停的查找,看有没有右边那个构造函数的原型对象。

例如child1 instanceof Child的查找顺序:

child1 -> child1.__proto__ -> Child.prototype
复制代码

child1 instanceof Parent的查找顺序:

child1 -> child1.__proto__ -> Child.prototype
-> Child.prototype.__proto__ -> Parent.prototype
复制代码

还不理解?

没关系,我还有大招:

我在上面👆原型链继承的思维导图上加了三个查找路线。

被⭕️标记的1、2、3分别代表的是Child、Parent、Object的原型对象。

好滴,一张图简洁明了。以后再碰到instanceof这种东西,按照我图上的查找路线来查找就可以了 😁 ~

(如果你能看到这里,你就会发现霖呆呆的美术功底,不是一般的强)

[表情包害羞~]

2.2 题目二

(了解isPrototypeOf()的使用)

既然说到了instanceof,那么就不得不提一下isPrototypeOf这个方法了。

它属于Object.prototype上的方法,这点你可以将Object.prototype打印在控制台中看看。

isPrototypeOf()的用法和instanceof相反。

它是用来判断指定对象object1是否存在于另一个对象object2的原型链中,是则返回true,否则返回false

例如还是上面👆这道题,我们将要打印的内容改一下:

function Parent () {
  this.name = 'parent'
}
function Child () {
  this.sex = 'boy'
}
Child.prototype = new Parent()
var child1 = new Child()

console.log(Child.prototype.isPrototypeOf(child1))
console.log(Parent.prototype.isPrototypeOf(child1))
console.log(Object.prototype.isPrototypeOf(child1))
复制代码

这里输出的依然是三个true

true
true
true
复制代码

判断的方式只要把原型链继承instanceof查找思维导图这张图反过来查找即可。

3. 构造继承

了解了最简单的原型链继承,再让我们来看看构造继承呀,也叫做构造函数继承

在子类构造函数内部使用call或apply来调用父类构造函数

为了方便你查看,我们先来复习一波.callapply方法。

3.1 题目一

(构造继承的基本原理)

所以来看看这道题?

function Parent (name) {
  this.name = name
}
function Child () {
  this.sex = 'boy'
  Parent.call(this, 'child')
}
var child1 = new Child()
console.log(child1)
复制代码

child1中会有哪些属性呢?

首先sex我们知道肯定会有的,毕竟它就是构造函数Child里的。

其次,我们使用了Parent.call(this, 'child').call函数刚刚已经说过了,它是会立即执行的,而这里又用了.call来改变Parent构造函数内的指向,所以我们是不是可以将它转化为伪代码:

function Child () {
	this.sex = 'boy'
	// 伪代码
	this.name = 'child'
}
复制代码

你就理解为相当于是直接执行了Parent里的代码。使用父类的构造函数来增强子类实例,等于是复制父类的实例属性给子类。

所以构造继承的原理就是:

在子类构造函数内部使用call或apply来调用父类构造函数

同样的,来写下伪代码:

function Child () {
    Parent.call(this, ...arguments)
}
复制代码

arguments表示的是你可以往里面传递参数,当然这只是伪代码)

3.2 题目二

如果你觉得上面👆这道题还不具有说明性,我们来看看这里。

现在我在子类和父类中都加上name这个属性,你觉得生出来的会是好孩子还是坏孩子呢?

function Parent (name) {
  this.name = name
}
function Child () {
  this.sex = 'boy'
  Parent.call(this, 'good boy')
  this.name = 'bad boy'
}
var child1 = new Child()
console.log(child1)
复制代码

其实是好是坏很好区分,只要想想3.1里,把Parent.call(this, 'good boy')换成伪代码就知道了。

换成了伪代码之后,等于是重复定义了两个相同名称的属性,当然是后面的覆盖前面的啦。

所以结果为:

Child {sex: "boy", name: "bad boy"}
复制代码

这道题如果换一下位置:

function Child () {
  this.sex = 'boy'
  this.name = 'bad boy'
  Parent.call(this, 'good boy')
}
复制代码

这时候就是好孩子了。

(哎,霖呆呆的产生可能就是第二种情况...)

3.3 题目三

(构造继承的优点)

解决了原型链继承中子类共享父类引用对象的问题

刚刚的题目都是一些基本数据类型,让我来加上引用类型看看

function Parent (name, sex) {
  this.name = name
  this.sex = sex
  this.colors = ['white', 'black']
}
function Child (name, sex) {
  Parent.call(this, name, sex)
}
var child1 = new Child('child1', 'boy')
child1.colors.push('yellow')

var child2 = new Child('child2', 'girl')
console.log(child1)
console.log(child2)
复制代码

这道题看着和1.3好像啊,没错,在父类构造函数中有一个叫colors的数组,它是地址引用的。

原型链继承中我们知道,子类构造函数创建的实例是会查找到原型链上的colors的,而且改动它会影响到其它的实例,这是原型链继承的一大缺点。

而现在呢?你看看使用了构造继承,结果为:

Child{ name: 'child1', sex: 'boy', colors: ['white', 'black', 'yellow'] }
Child{ name: 'child2', sex: 'girl', colors: ['white', 'black'] }
复制代码

我们发现修改child1.colors并不会影响到其它的实例(child2)耶。

这里的原因其实我们前面也说了:

使用父类的构造函数来增强子类实例,等于是复制父类的实例属性给子类。

所以现在child1child2现在分别有它们各自的colors了,就不共享了。

而且这种拷贝属于深拷贝,验证的方式是你可以把colors数组中的每一项改为一个对象,然后修改它看看。

function Parent () {
	//...
	this.colors = [{ title: 'white' }, { title: 'black' }]
}
复制代码

因此我们可以得出构造继承的优点:

3.4 题目四

(构造继承的缺点一)

在了解继承的时候,我们总是会想到原型链上的属性和方法能不能被继承到。

采用了这种构造继承的方式,能不能继承父类原型链上的属性呢?

来看下面👇这道题目

function Parent (name) {
  this.name = name
}
Parent.prototype.getName = function () {
  console.log(this.name)
}
function Child () {
  this.sex = 'boy'
  Parent.call(this, 'good boy')
}
Child.prototype.getSex = function () {
  console.log(this.sex)
}
var child1 = new Child()
console.log(child1)
child1.getSex()
child1.getName()
复制代码

我给子类和父类的原型对象上都分别加了一个方法,然后调用它们。

结果竟然是:

Child {sex: "boy", name: "good boy"}
'boy'
Uncaught TypeError: child1.getName is not a function
复制代码

你确实是不配使用我。

你使用Parent.call(this, 'good boy')只不过是让你复制了一下我构造函数里的属性和方法,可没说能让你复制我原型对象的啊~年轻人,不要这么贪嘛。

所以我们可以看出构造继承一个最大的缺点,那就是:

小气!

"啪!"

"你给我正经点"😂

其实是:

"那不就是小气嘛..."

"..."

3.5 题目五

(构造继承的缺点二)

它的第二个缺点是:实例并不是父类的实例,只是子类的实例。

停一下,让我们先来思考一下这句话的意思,然后想想怎样来验证它呢 🤔️ ?

一分钟...二分钟...三分钟...

啊,我知道了,刚刚不是才学的一个叫instanceof的运算符吗?它就能检测某个实例的原型链上能不能找到构造函数的原型对象。

换句话说就能检测某个对象是不是某个构造函数的实例啦。

所以让我们来看看:

function Parent (name) {
  this.name = name
}
function Child () {
  this.sex = 'boy'
  Parent.call(this, 'child')
}
var child1 = new Child()

console.log(child1)
console.log(child1 instanceof Child)
console.log(child1 instanceof Parent)
console.log(child1 instanceof Object)
复制代码

结果为:

Child {sex: "boy", name: "child"}
true
false
true
复制代码

(虽说构造继承出来的实例确实不是父类的实例,只是子类的实例。但我其实是不太明白教材中为什么要说它是一个缺点呢?鄙人愚昧,想的可能是:子类生成的实例既然能用到父类中的属性和方法,那我就应该也要确定这些属性和方法的来源,如果不能使用instanceof检测到你和父类有关系的话,那就会对这些凭空产生的属性和方法有所质疑...)

因此构造继承第二个缺点是:

总结-构造继承

构造继承总结来说:

优点:

缺点:

(最后一个缺点‘无法实现函数复用’经过评论区小伙伴matteokjh的提醒,我理解的大概是这个意思:父类构造函数中的某个函数可能只是一个功能型的函数,它不论被复制了多少份,输出的结果或者功能都是一样的,那么这类函数是完全可以拿来复用的。但是现在用了构造函数继承,由于它是复制了父类构造函数中的属性和方法,这样产生的每个子类实例中都会有一份自己各自的方法,可是有的方法完全没有必要复制,可以用来共用的,所以就说不能够「函数复用」。)

4. 组合继承

既然原型链继承构造继承都有这么多的缺点,那我们为何不阴阳结合,把它们组合在一起呢?

咦~

好像是个好想法。

把我们前面的伪代码拿来用用,想想该如何组合呢?

// 原型链继承
Child.prototype = new Parent()
// 构造继承
function Child () {
  Parent.call(this, ...arguments)
}
复制代码

...思考中🤔...

看到这两段伪代码,我好像有所顿悟了,不就是按照伪代码里写的,把这两种继承组合在一起吗?

哇!这都被我猜中了,搜索一下组合继承的概念,果然就是这样。

组合继承的概念:

组合继承就是将原型链继承与构造函数继承组合在一起,从而发挥两者之长的一种继承模式。

思路:

基操:

基操中的第一点就是构造继承,第二点为原型链继承,第三点其实只是一个好的惯例,在后面的题目会细讲到它。

4.1 题目一

(理解组合继承的基本使用)

现在我决定对你们不再仁慈,让我们换种想法,逆向思维来解解题好不好。

阴笑~

既然我都已经说了这么多关于组合继承的东西了,那想必你们也知道该如何设计一个组合继承了。

我现在需要你们来实现这么一个ChildParent构造函数(代码尽可能地少),让它们代码的执行结果能如下:

(请先不要着急看答案哦,花上2分钟来思考一下,弄清每个属性在什么位置上,都有什么公共属性就好办了)

var child1 = new Child('child1')
var parent1 = new Parent('parent1')
console.log(child1) // Child{ name: 'child1', sex: 'boy' }
console.log(parent1)// Parent{ name: 'parent1' }
child1.getName()    // 'child1'
child1.getSex()     // 'boy'
parent1.getName()   // 'parent1'
parent1.getSex()    // Uncaught TypeError: parent1.getSex is not a function
复制代码

解题思路:

好的👌,每个属性各自在什么位置上都已经找到了,再来看看如何实现它吧:

function Parent (name) {
  this.name = name
}
Parent.prototype.getName = function () {
  console.log(this.name)
}
function Child (name) {
  this.sex = 'boy'
  Parent.call(this, name)
}
Child.prototype = new Parent()
Child.prototype.getSex = function () {
  console.log(this.sex)
}

var child1 = new Child('child1')
var parent1 = new Parent('parent1')
console.log(child1)
console.log(parent1)
child1.getName()
child1.getSex()
parent1.getName()
parent1.getSex()
复制代码

不知道是不是和你构想的一样呢 🤔️?

其实这是一道开放式题,如果构想的不一样也是正常了,不过你得自己把自己构想的用代码跑一边看看是不是和需求一样。

为什么说它比较开放呢?

就比如第一点,name属性,它不一定就只存在于Parent里呀,我Child里也可以有一个自己的name属性,只不过题目要求代码尽可能地少,所以最好的就是存在与Parent中,并且用.call来实现构造继承

另外,getName方法也不一定要在Parent.prototype上,它只要存在于parent1的原型链中就可以了,所以也有可能在Object.prototype,脑补一下那张原型链的图,是不是这样呢?

这就是组合继承带来的魅力,如果你能看懂这道题,就已经掌握其精髓了 👏。

4.2 题目二

(理解constructor有什么作用)

拿上面👆那道题和最开始我们定义组合继承的基操做对比,发现第三点constructor好像并没有提到耶,但是也实现了我们想要的功能,那这样说来constructor好像并没有什么软用呀...

你想的没错,就算我们不对它进行任何的设置,它也丝毫不会影响到JS的内部属性。

它不过是给我们一个提示,用来标示实例对象是由哪个构造函数创建的。

先用一张图来看看constructor它存在的位置吧:

可以看到,它实际就是原型对象上的一个属性,指向的是构造函数。

所以我们是不是可以有这么一层对应关系:

guaiguai.__proto__ = Cat.prototype
Cat.prototype.constructor = Cat
guaiguai.__proto__.constructor = Cat
复制代码

(结合图片来看,这样的三角恋关系俨然并不复杂)

再结合题目4.1来看,你觉得以下代码会打印出什么呢?题目其实还是4.1的题目,要求打印的东西不同而已。

function Parent (name) {
  this.name = name
}
Parent.prototype.getName = function () {
  console.log(this.name)
}
function Child (name) {
  this.sex = 'boy'
  Parent.call(this, name)
}
Child.prototype = new Parent()
Child.prototype.getSex = function () {
  console.log(this.sex)
}

var child1 = new Child('child1')
var parent1 = new Parent('parent1')
console.log(child1.constructor)
console.log(parent1.constructor)
复制代码

一时不知道答案也没关系,我直接公布一下了:

f Parent () {}
f Parent () {}
复制代码

打印出的两个都是Parent函数。

parent1.constructorParent函数这个还好理解,结合上面👆的图片来看,只要通过原型链查找,我parent1实例自身没有constructor属性,那我就拿原型上的constructor,发现它指向的是构造函数Parent,因此第二个打印出Parent函数。

而对于child1,想想组合继承用到了原型链继承,虽然也用到了构造继承,但是构造继承对原型链之间的关系没有影响。那么我组合继承的原型链关系是不是就可以用原型链继承那张关系图来看?

如下:

就像上面看到的一样,原型链继承切断了原本ChildChild原型对象的关系,而是重新指向了匿名实例。使得实例child1能够使用匿名实例原型链上的属性和方法。

当我们想要获取child1.constructor,肯定是向上查找,通过__proto__找它构造函数的原型对象匿名实例

但是匿名实例它自身是没有constructor属性的呀,它只是Parent构造函数创建出来的一个对象而已,所以它也会继续向上查找,然后就找到了Parent原型对象上的constructor,也就是Parent了。

所以回过头来看看这句话:

construcotr它不过是给我们一个提示,用来标示实例对象是由哪个构造函数创建的。

从人(常)性(理)的角度上来看,child1Child构建的,parent1Parent构建的。

那么child1它的constructor就应该是Child呀,但是现在却变成了Parent,貌似并不太符合常理啊。

所以才有了这么一句:

Child.prototype.constructor = Child
复制代码

用以修复constructor的指向。

现在让我们通过改造原型链继承思维导图来画画组合继承的思维导图吧。

(至于为什么在组合继承中我修复了constructor,在原型链继承中没有,这个其实取决于你自己,因为你也看到了constructor实际并没有什么作用,不过面试被问到的话肯定是要知道的)

总结来说:

4.3 题目三

constructor的某个使用场景)

先来看看下面👇这道题:

var a;
(function () {
  function A () {
    this.a = 1
    this.b = 2
  }
  A.prototype.logA = function () {
    console.log(this.a)
  }
  a = new A()
})()

a.logA()
复制代码

这里的输出结果:

1
复制代码

乍一看被整片的a给搞糊了,但是仔细分析来,就能得出结果了。

难度升级:

现在我想要在匿名函数外给A这个构造函数的原型对象中添加一个方法logB用以打印出this.b

你首先想到的是不是B.prototype.logB = funciton() {}

但是注意咯,我是要你在匿名函数外添加,而此时由于作用域的原因,我们在匿名函数外是访问不到A的,所以这样的做法就不可行了。

解决办法:

虽然我们在外层访问不到A,但是我们可以通过原型链查找,来获取A的原型对象呀。

还是这张图:

这里我们就有两种解决办法了:

  1. 通过a.__proto__来访问到原型对象:
a.__proto__.logB = function () {
  console.log(this.b)
}
a.logB()
复制代码
  1. 通过a.constructor.prototype来访问到原型对象:
a.constructor.prototype.logB = function () {
  console.log(this.b)
}
a.logB()
复制代码

想想是不是这样的?

虽然我a实例上没有constructor,但是原型对象上有呀,所以a.construtor实际拿的是原型对象上的construtor

(个人愚见感觉并没什么软用...我用__proto__就可以了呀 😂)

4.4 题目四

(理解组合继承的优点)

function Parent (name, colors) {
  this.name = name
  this.colors = colors
}
Parent.prototype.features = ['cute']
function Child (name, colors) {
  this.sex = 'boy'
  Parent.apply(this, [name, colors])
}
Child.prototype = new Parent()
Child.prototype.constructor = Child

var child1 = new Child('child1', ['white'])
child1.colors.push('yellow')
child1.features.push('sunshine')
var child2 = new Child('child2', ['black'])

console.log(child1)
console.log(child2)
console.log(Child.prototype)

console.log(child1 instanceof Child)
console.log(child1 instanceof Parent)
复制代码

有了前面几题作为基础,这道题也就不难了。

答案:

Child{ sex: "boy", name: "child1", colors: ["white", "yellow"] }
Child{ sex: "boy", name: "child2", colors: ["black"] }
Parent{ name: undefined, colors: undefined, constructor: f Child () {} }

true
true
复制代码

解析思路:

现在你就可以看出组合继承的优点了吧,它其实就是将两种继承方式的优点给结合起来。

4.5 题目五

(理解组合继承的缺点)

人无完人,狗无完狗,就算是组合继承这么牛批的继承方式也还是有它的缺点 😁。

一起来看看这里:

function Parent (name) {
  console.log(name) // 这里有个console.log()
  this.name = name
}
function Child (name) {
  Parent.call(this, name)
}
Child.prototype = new Parent()
var child1 = new Child('child1')

console.log(child1)
console.log(Child.prototype)
复制代码

执行结果为:

undefined
'child1'

Child{ name: 'child1' }
Parent{ name: undefined }
复制代码

我们虽然只调用了new Child()一次,但是在Parent中却两次打印出了name

也就是说,在使用组合继承的时候,会凭空多调用一次父类构造函数。

另外,我们想要继承父类构造函数里的属性和方法采用的是构造继承,也就是复制一份到子类实例对象中,而此时由于调用了new Parent(),所以Child.prototype中也会有一份一模一样的属性,就例如这里的name: undefined,可是我子类实例对象自己已经有了一份了呀,所以我怎么也用不上Child.prototype上面的了,那你这凭空多出来的属性不就占了内存浪费了吗?

因此我们可以看出组合继承的缺点:

4.6 题目六

(考察你是否理解实例对象上引用类型和原型对象上引用类型的区别)

这里可就有一个坑了,得注意了⚠️:

function Parent (name, colors) {
  this.name = name
  this.colors = colors
}
Parent.prototype.features = ['cute']
function Child (name, colors) {
  Parent.apply(this, [name, colors])
}
Child.prototype = new Parent()
Child.prototype.constructor = Child

var child1 = new Child('child1', ['white'])
child1.colors.push('yellow')
child1.features.push('sunshine')
var child2 = new Child('child2', ['black'])

console.log(child1.colors)
console.log(child2.colors)
console.log(child1.features)
console.log(child2.features)
复制代码

题目解析:

结果为:

["white", "yellow"]
["black"]
["cute", "sunshine"]
["cute", "sunshine"]
复制代码

可是霖呆呆不对呀,你刚刚不是还说了:

组合继承弥补了原型链继承中引用属性共享的问题

就在题4.4中,都还热乎着呢?怎么这里的features还是没有被解决啊,它们还是共享了。

"冤枉啊!我从来不骗人"

它确实是解决了原型链继承中引用属性共享的问题啊,你想想这里Child.prototype是谁?

是不是new Parent()产生的那个匿名实例?而这个匿名实例中的引用类型是不是colors?而colors是不是确实不是共享的?

那就对了呀,我已经帮你解决了原型(匿名实例)中引用属性共享的问题了呀。

至于featuresParent.prototype上的属性,相当于是爷爷那一级别的了,这我可没法子。

总结-组合继承

同样的,让我们对组合继承也来做个总结吧:

实现方式:

优点:

缺点:

constructor总结:

5. 寄生组合继承

唔...寄生这个词听着有点可怕啊...

它比组合继承还要牛批一点。

刚刚我们提了组合继承的缺点无非就是:

  1. 父类构造函数会被调用两次
  2. 生成了两个实例,在父类实例上产生了无用废弃的属性

那么有没有一种方式让我们直接跳过父类实例上的属性,而让我直接就能继承父类原型链上的属性呢?

也就是说,我们需要一个干净的实例对象,来作为子类的原型。并且这个干净的实例对象还得能继承父类原型对象里的属性。

咦~说到干净的对象,我就想到了一个方法:Object.create()

让我们先来回忆一波它的用法:

Object.create(proto, propertiesObject)
复制代码

在这里我们主要讲解一下第一个参数proto,它的作用就是能指定你要新建的这个对象它的原型对象是谁。

怎么说呢?

就好比,我们使用var parent1 = new Parent()创建了一个对象parent1,那parent1.__proto__就是Parent.prototype

使用var obj = new Object()创建了一个对象obj,那obj.__proto__就是Object.prototype

而这个Object.create()屌了,它现在能指定你新建对象的__proto__

哈哈哈哈~

这正不是我们想要的吗?我们现在只想要一个干净并且能链接到父类原型链上的对象。

来看看题目一。

5.1 题目一

(理解寄生组合继承的用法)

function Parent (name) {
  this.name = name
}
Parent.prototype.getName = function () {
  console.log(this.name)
}
function Child (name) {
  this.sex = 'boy'
  Parent.call(this, name)
}
// 与组合继承的区别
Child.prototype = Object.create(Parent.prototype)

var child1 = new Child('child1')

console.log(child1)
child1.getName()

console.log(child1.__proto__)
console.log(Object.create(null))
console.log(new Object())
复制代码

可以看到,上面👆这道题就是一个标准的寄生组合继承,它与组合继承的区别仅仅是Child.prototype不同。

我们使用了Object.create(Parent.prototype)创建了一个空的对象,并且这个对象的__proto__属性是指向Parent.prototype的。

来看看寄生组合继承的思维导图:

(灵魂画手再次上线)

可以看到,现在Parent()已经和child1没有关系了,仅仅是用了Parent.call(this)来复制了一下Parent里的属性和方法 😁。

因此这道题的答案为:

function Parent (name) {
  this.name = name
}
Parent.prototype.getName = function () {
  console.log(this.name)
}
function Child (name) {
  this.sex = 'boy'
  Parent.call(this, name)
}
// 与组合继承的区别
Child.prototype = Object.create(Parent.prototype)
Child.prototype.constructor = Child

var child1 = new Child('child1')

console.log(child1) // Child{ sex: "boy", name: "child1" }
child1.getName() // "child1"

console.log(child1.__proto__) // Parent{}
console.log(Object.create(null)) // {}
console.log(new Object()) // {}
复制代码

题目解析:

最后的三个空对象,我们就需要展开来看看了:

(看看,大家在学继承的同时,还顺便学习了一波Object.create(),多好啊 😁)

5.2 题目二

虽然寄生组合继承组合继承非常像,不过我们还是来看一道题巩固巩固吧。

执行结果:

Child{ name: 'child1', face: 'smile', sex: 'boy', colors: ['white', 'black', 'yellow'] }
Child{ name: 'child2', face: 'smile', sex: 'boy', colors: ['white', 'black'], features: ['sunshine'] }

["cute"]
["sunshine"]
复制代码

哈哈哈,小伙伴们的答案和这里是否有出入呢?

是不是发现一不小心就会做错 😂。

让我们来看看解题思路:

(这道题我是使用VSCode插件Polacode-2019做的代码截图,不知道大家是喜欢这种代码截图还是喜欢源代码的形式呢?可以留言告诉霖呆呆 😁)

(另外,关于更多美化工具的使用可以查看我的这篇文章:你的掘金文章本可以这么炫(博客美化工具一波带走)

总结-寄生组合继承

寄生组合继承算是ES6之前一种比较完美的继承方式吧。

它避免了组合继承中调用两次父类构造函数,初始化两次实例属性的缺点。

所以它拥有了上述所有继承方式的优点:

6. 原型式继承

算是翻了很多关于JS继承的文章吧,其中百分之九十都是这样介绍原型式继承的:

该方法的原理是创建一个构造函数,构造函数的原型指向对象,然后调用 new 操作符创建实例,并返回这个实例,本质是一个浅拷贝。

伪代码如下:

(后面会细讲)

function objcet (obj) {
    function F () {};
    F.prototype = obj;
    F.prototype.constructor = F;
    return new F();
}
复制代码

开始以为是多神秘的东西,但后来真正了解了它之后感觉用的应该不多吧... 😢

先来看看题目一。

6.1 题目一

在真正开始看原型式继承之前,先来看个我们比较熟悉的东西:

var cat = {
  heart: '❤️',
  colors: ['white', 'black']
}

var guaiguai = Object.create(cat)
var huaihuai = Object.create(cat)

console.log(guaiguai)
console.log(huaihuai)

console.log(guaiguai.heart)
console.log(huaihuai.colors)
复制代码

这里的执行结果:

{}
{}

'❤️'
['white', 'black']
复制代码

这里用到了我们之前提到过的Object.create()方法。

在这道题中,Object.create(cat)会创建出一个__proto__属性为cat的空对象。

所以你可以看到乖乖坏坏都是一只空猫,但是它们却能用猫cat的属性。

6.2 题目二

不怕你笑话,上面👆说的这种方式就是原型式继承,只不过在ES5之前,还没有Object.create()方法,所以就会用开头介绍的那段伪代码来代替它。

将题目6.1改造一下,让我们自己来实现一个Object.create()

我们就将要实现的函数命名为create()

想想Object.create()的作用:

所以就有了这么一个方法:

function objcet (obj) {
    function F () {};
    F.prototype = obj;
    F.prototype.constructor = F;
    return new F();
}
复制代码

它满足了上述的几个条件。

来看看效果是不是和题6.1一样呢?

function objcet (obj) {
    function F () {};
    F.prototype = obj;
    F.prototype.constructor = F;
    return new F();
}
var cat = {
  heart: '❤️',
  colors: ['white', 'black']
}

var guaiguai = create(cat)
var huaihuai = create(cat)

console.log(guaiguai)
console.log(huaihuai)

console.log(guaiguai.heart)
console.log(huaihuai.colors)
复制代码

执行结果为:

效果是和Object.create()差不多(只不过我们自定义的create返回的对象是构造函数F创建的)。

这就有小伙伴要问了,既然是需要满足

这个条件的话,我这样写也可以实现啊:

function create (obj) {
    var newObj = {}
    newObj.__proto__ = obj
    return newObj;
}
复制代码

请注意了,我们是要模拟Object.create()方法,如果你都能使用__proto__,那为何不干脆使用Object.create()呢?(它们是同一时期的产物)

总结-原型式继承

由于它使用的不太多,这里就不多说它了。

(霖呆呆就是这么现实)

不过还是要总结一下滴:

实现方式:

该方法的原理是创建一个构造函数,构造函数的原型指向对象,然后调用 new 操作符创建实例,并返回这个实例,本质是一个浅拷贝。

ES5之后可以直接使用Object.create()方法来实现,而在这之前就只能手动实现一个了(如题目6.2)。

优点:

缺点:

(呀!好久没用表情包了,此处应该有个表情包)

7. 寄生式继承

cccc...

怎么又来了个什么寄生式继承啊,还有完没完...

心态放平和...

其实这个寄生式继承也没啥东西的,它就是在原型式继承的基础上再封装一层,来增强对象,之后将这个对象返回。

来看看伪代码你就知道了:

function createAnother (original) {
    var clone = Object.create(original);; // 通过调用 Object.create() 函数创建一个新对象
    clone.fn = function () {}; // 以某种方式来增强对象
    return clone; // 返回这个对象
}
复制代码

7.1 题目一

(了解寄生式继承的使用方式)

它的使用方式,唔...

例如我现在想要继承某个对象上的属性,同时又想在新创建的对象中新增上一些其它的属性。

来看下面👇这两只猫咪

var cat = {
  heart: '❤️',
  colors: ['white', 'black']
}
function createAnother (original) {
    var clone = Object.create(original);
    clone.actingCute = function () {
      console.log('我是一只会卖萌的猫咪')
    }
    return clone;
}
var guaiguai = createAnother(cat)
var huaihuai = Object.create(cat)

guaiguai.actingCute()
console.log(guaiguai.heart)
console.log(huaihuai.colors)
console.log(guaiguai)
console.log(huaihuai)
复制代码

题目解析:

执行结果:

'我是一只会卖萌的猫咪'
'❤️'
['white', 'black']
{ actingCute: ƒ }
{}
复制代码

总结-寄生式继承

实现方式:

优点:

缺点:

8. 混入方式继承多个对象

过五关斩六将,咱终于到了ES5中的要讲的最后一种继承方式了。

这个混入方式继承其实很好玩,之前我们一直都是以一个子类继承一个父类,而混入方式继承就是教我们如何一个子类继承多个父类的。

在这边,我们需要用到ES6中的方法Object.assign()

它的作用就是可以把多个对象的属性和方法拷贝到目标对象中,若是存在同名属性的话,后面的会覆盖前面。(当然,这种拷贝是一种浅拷贝啦)

来看看伪代码:

function Child () {
    Parent.call(this)
    OtherParent.call(this)
}
Child.prototype = Object.create(Parent.prototype)
Object.assign(Child.prototype, OtherParent.prototype)
Child.prototype.constructor = Child
复制代码

8.1 题目一

(理解混入方式继承的使用)

额,既然您都看到这了,说明实力以及很强了,要不?咱直接就上个复杂点的题?

function Parent (sex) {
  this.sex = sex
}
Parent.prototype.getSex = function () {
  console.log(this.sex)
}
function OtherParent (colors) {
  this.colors = colors
}
OtherParent.prototype.getColors = function () {
  console.log(this.colors)
}
function Child (sex, colors) {
  Parent.call(this, sex)
  OtherParent.call(this, colors) // 新增的父类
  this.name = 'child'
}
Child.prototype = Object.create(Parent.prototype)
Object.assign(Child.prototype, OtherParent.prototype) // 新增的父类原型对象
Child.prototype.constructor = Child

var child1 = new Child('boy', ['white'])
child1.getSex()
child1.getColors()
console.log(child1)
复制代码

这里就是采用了混入方式继承,在题目中标出来的地方就是不同于寄生组合继承的地方。

现在的child1不仅复制了Parent上的属性和方法,还复制了OtherParent上的。

而且它不仅可以使用Parent.prototype的属性和方法,还能使用OtherParent.prototype上的。

结果:

'boy'
['white']
{ name: 'child', sex: 'boy', colors: ['white'] }
复制代码

8.2 题目二

(理解混入方式继承的原型链结构)

同是上面👆的题,我现在多加上几个输出:

function Parent (sex) {
  this.sex = sex
}
Parent.prototype.getSex = function () {
  console.log(this.sex)
}
function OtherParent (colors) {
  this.colors = colors
}
OtherParent.prototype.getColors = function () {
  console.log(this.colors)
}
function Child (sex, colors) {
  Parent.call(this, sex)
  OtherParent.call(this, colors) // 新增的父类
  this.name = 'child'
}
Child.prototype = Object.create(Parent.prototype)
Object.assign(Child.prototype, OtherParent.prototype) // 新增的父类原型对象
Child.prototype.constructor = Child

var child1 = new Child('boy', ['white'])
// child1.getSex()
// child1.getColors()
// console.log(child1)

console.log(Child.prototype.__proto__ === Parent.prototype)
console.log(Child.prototype.__proto__ === OtherParent.prototype)
console.log(child1 instanceof Parent)
console.log(child1 instanceof OtherParent)
复制代码

这四个输出你感觉会是什么 🤔️?

先不要着急,如果有条件的,自己动手在纸上把现在的原型链关系给画一下。

反正呆呆是已经用XMind的画好了:

可以看到,其实它与前面我们画的寄生组合继承思维导图就多了下面OtherParent的那部分东西。

根据这这幅图,我们很快就能得出答案了:

true
false
true
false
复制代码

9. class中的继承

构造函数中主要的几种继承方式都已经介绍的差不多了,接下来就让我们看看ES6class的继承吧。

class 中继承主要是依靠两个东西:

而且对于该继承的效果和之前我们介绍过的寄生组合继承方式一样。(没错,就是那个最屌的继承方式)

一起来看看题目一 😁。

9.1 题目一

(理解class中的继承)

既然它的继承和寄生组合继承方式一样,那么让我们将题目5.1的题目改造一下,用class的继承方式来实现它。

class Parent {
  constructor (name) {
    this.name = name
  }
  getName () {
    console.log(this.name)
  }
}
class Child extends Parent {
  constructor (name) {
    super(name)
    this.sex = 'boy'
  }
}
var child1 = new Child('child1')
console.log(child1)
child1.getName()

console.log(child1 instanceof Child)
console.log(child1 instanceof Parent)
复制代码

结果如下:

Child{ name: 'child1', sex: 'boy' }
'child1'
true
true
复制代码

再让我们来写一下寄生组合继承的实现方式:

function Parent (name) {
  this.name = name
}
Parent.prototype.getName = function () {
  console.log(this.name)
}
function Child (name) {
  this.sex = 'boy'
  Parent.call(this, name)
}
Child.prototype = Object.create(Parent.prototype)
Child.prototype.constructor = Child

var child1 = new Child('child1')
console.log(child1)
child1.getName()

console.log(child1 instanceof Child)
console.log(child1 instanceof Parent)
复制代码

结果如下:

Child{ name: 'child1', sex: 'boy' }
'child1'
true
true
复制代码

这样好像看不出个啥,没事,让我们上图:

class继承

寄生组合继承

可以看到,class的继承方式完全满足于寄生组合继承。

9.2 题目二

(理解extends的基本作用)

可以看到上面👆那道题,我们用到了两个关键的东西:extendssuper

extends从字面上来看还是很好理解的,对某个东西的延伸,继承。

那如果我们单单只用extends不用super呢?

class Parent {
  constructor (name) {
    this.name = name
  }
  getName () {
    console.log(this.name)
  }
}
class Child extends Parent {
  // constructor (name) {
  //   super(name)
  //   this.sex = 'boy'
  // }
  sex = 'boy' // 实例属性sex放到外面来
}
var child1 = new Child('child1')
console.log(child1)
child1.getName()
复制代码

其实这里的执行结果和没有隐去之前一样。

执行结果:

那我们是不是可以认为:

class Child extends Parent {}

// 等同于
class Child extends Parent {
    constructor (...args) {
        super(...args)
    }
}
复制代码

OK👌,其实这一步很好理解啦,还记得之前我们就提到过,在class中如果没有定义constructor方法的话,这个方法是会被默认添加的,那么这里我们没有使用constructor,它其实已经被隐式的添加和调用了。

所以我们可以看出extends的作用:

9.3 题目三

(理解super的基本作用)

通过上面那道题看来,constructor貌似是可有可无的角色。

那么super呢,它在 class中扮演的是一个什么角色 🤔️?

还是上面的题目,但是这次我不使用super,看看会有什么效果:

class Parent {
  constructor () {
    this.name = 'parent'
  }
}
class Child extends Parent {
  constructor () {
    // super(name) // 把super隐去
  }
}
var child1 = new Child()
console.log(child1)
child1.getName()
复制代码

哈哈哈,现在你保存刷新页面,就会发现它报错了:

Uncaught ReferenceError: Must call super constructor in derived class before accessing 'this' or returning from derived constructor
    at new Child
复制代码

你品你细细品。

大致意思就是你必须得在constructor中调用一下super函数。

这样说来,constructorsuper是一对好基友啊...

super函数咱还是不能省,很重要啊。

然后再看了看它的写法,有点像是给父级类中传递参数的感觉啊 😄。

唔...如果你这样想的话算是猜对了一部分吧。这其实和ES6的继承机制有关。

通俗理解就是,子类必须得在constructor中调用super方法,否则新建实例就会报错,因为子类自己没有自己的this对象,而是继承父类的this对象,然后对其加工,如果不调用super的话子类就得不到this对象。

哇哦~

[果然是好基友~]

这道题介绍的是super的基本作用,下面来说说它的具体用法吧。

9.4 题目四

(super当作函数调用时)

super其实有两种用法,一种是当作函数来调用,还有一种是当做对象来使用。

之前那道题就是将它当成函数来调用的,而且我们知道在constructor中还必须得执行super()

其实,super被当作函数调用时,代表着父类的构造函数

虽然它代表着父类的构造函数,但是返回的却是子类的实例,也就是说super内部的this指向的是Child

让我们来看道题验证一下:

(new.target指向当前正在执行的那个函数,你可以理解为new后面的那个函数)

class Parent {
  constructor () {
    console.log(new.target.name)
  }
}
class Child extends Parent {
  constructor () {
    var instance = super()
    console.log(instance)
    console.log(instance === this)
  }
}
var child1 = new Child()

var parent1 = new Parent()

console.log(child1)
console.log(parent1)
复制代码

这道题中,我在父类的constructor中打印出new.target.name

并且用了一个叫做instance的变量来盛放super()的返回值。

而刚刚我们已经说了,super的调用代表着父类构造函数,那么这边我在调用new Child的时候,它里面也执行了父类的constructor函数,所以console.log(new.target.name)肯定被执行了两遍了(一遍是new Child,一遍是new Parent)

所以这里的执行结果为:

'Child'
Child{}
true

'Parent'

Child{}
Parent{}
复制代码

通过这道题我们可以看出:

9.5 题目五

(super当成函数调用时的限制)

刚刚已经说明了super当成函数调用的时候就相当于是用call来改变了父类构造函数中的this指向,那么它的使用有什么限制呢?

来看看这里:

class Parent {
  constructor (name) {
    this.name = name
  }
}
class Child extends Parent {
  constructor (name) {
    this.sex = 'boy'
    super(name)
  }
}
var child1 = new Child('child1')
console.log(child1)
复制代码

你觉得这里会打印出什么呢 🤔️?

其实这里啥都不会打印,控制台是红色的。

报了个和7.3一样的错:

Uncaught ReferenceError: Must call super constructor in derived class before accessing 'this' or returning from derived constructor
    at new Child
复制代码

这也就符合了刚刚说到的第一点:子类constructor中如果要使用this的话就必须放到super()之后。

这点其实非常好理解,还记得super的作用吗?在constructor中必须得有super(),它就是用来产生实例this的,那么再调用它之前,肯定是访问不到this的啦。

也就是在this.sex = 'boy'这一步的时候就已经报错了。

至于第二点,super被当成函数来调用的话就必须得放到constructor中,在其它的地方使用它就是我们接下来要说的super当成对象使用的情况。

9.6 题目六

(super当成对象来使用时)

super如果当成一个对象来调用的话,唔...那也可能存在于class里的不同地方呀。

比如constructor、子类实例方法、子类构造方法,在这些地方它分别指代的是什么呢?

我们只需要记住:

依靠着这个准则,我们来做做下面👇这道题:

class Parent {
  constructor (name) {
    this.name = name
  }
  getName () {
    console.log(this.name)
  }
}
Parent.prototype.getSex = function () {
	console.log('boy')
}
Parent.getColors = function () {
  console.log(['white'])
}
class Child extends Parent {
  constructor (name) {
    super(name)
    super.getName()
  }
  instanceFn () {
    super.getSex()
  }
  static staticFn () {
    super.getColors()
  }
}
var child1 = new Child('child1')
child1.instanceFn()
Child.staticFn()
console.log(child1)
复制代码

通过学习《【何不三连】比继承家业还要简单的JS继承题-封装篇(牛刀小试)》我们知道各个方法所在的位置:

题目分析:

所以执行结果为:

'child1'
'boy'
['white']
Child{ name: 'child1' }
复制代码

"Good for you! 我貌似已经掌握它嘞"

9.7 题目七

(super当成对象调用父类方法时this的指向)

在做刚刚那道题的时候,额,你们就对super.getName()的打印结果没啥疑问吗 🤔️?

(难道是我吹的太有模有样让你忽略了它?)

既然super.getName()getName是被super调用的,而我却说此时的super指向的是父类原型对象。那么getName内打印出的应该是父类原型对象上的name,也就是undefined呀,怎么会打印出child1呢?

带着这个疑问我写下了这道题:

class Parent {
  constructor () {}
}
Parent.prototype.sex  = 'boy'
Parent.prototype.getSex = function () {
  console.log(this.sex)
}
class Child extends Parent {
  constructor () {
    super()
    this.sex = 'girl'
    super.getSex()
  }
}
var child1 = new Child()
console.log(child1)
复制代码

现在父类原型对象和子类实例对象child1上都有sex属性,且不相同。

如果按照this指向来看,调用super.getSex()打印出的应该是Parent.prototype上的sex'boy'

就像是这样调用一样:Parent.prototype.getSex()

但是结果却是:

'girl'
Child{ sex: 'girl' }
复制代码

唔...其实扯了这么一大堆,我只是想告诉你:

也就是说,super.getSex()转换为伪代码就是:

super.getSex.call(this)
// 即
Parent.prototype.getSex.call(this)
复制代码

(别看这里扯的多,但是多看点例子🌰的话理解一定会加深刻的)

而且super其实还有一个特性,就是你在使用它的时候,必须得显式的指定它是作为函数使用还是对象来使用,否则会报错的。

比如下面这样就不可以:

class Child extends Parent {
    constructor () {
        super() // 不报错
        super.getSex() // 不报错
        console.log(super) // 这里会报错
    }
}
复制代码

9.8 题目八

(了解extends的继承目标)

extends后面接着的继承目标不一定要是个class

class B extends A {},只要A是一个有prototype属性的函数,就能被B继承。

由于函数都有prototype属性,因此A可以是任意函数。

来看看这一题:

function Parent () {
  this.name = 'parent'
}

class Child1 extends Parent {}
class Child2 {}
class Child3 extends Array {}
var child1 = new Child1()
var child2 = new Child2()
var child3 = new Child3()
child3[0] = 1

console.log(child1)
console.log(child2)
console.log(child3)
复制代码

执行结果:

Child1{ name: 'parent' }
Child2{}
Child3[1]
复制代码

(其实这里只要作为一个知道的知识点就可以了,真正使用来说貌似不常用)

总结-class继承

我滴个乖乖...

class继承咋有这么多讲的啊。

不过总算是我也说完,你也看完了...

OK👌,来个总结呗。

ES6中的继承:

super相关:

ES5继承和ES6继承的区别:

所有继承总结

唔...写到最后我感觉还是要将所有的继承情况来做一个总结,这边只总结出实现方式的伪代码以及原型链思维导图,具体的优缺点在各个模块中已经总结好了就不重复了。

1. 原型链继承

伪代码:

Child.prototype = new Parent()
复制代码

思维导图:

2. 构造继承

伪代码:

function Child () {
    Parent.call(this, ...arguments)
}
复制代码

3. 组合继承

伪代码:

// 构造继承
function Child () {
  Parent.call(this, ...arguments)
}
// 原型链继承
Child.prototype = new Parent()
// 修正constructor
Child.prototype.constructor = Child
复制代码

思维导图:

4. 寄生组合继承

伪代码:

// 构造继承
function Child () {
  Parent.call(this, ...arguments)
}
// 原型式继承
Child.prototype = Object.create(Parent.prototype)
// 修正constructor
Child.prototype.constructor = Child
复制代码

思维导图:

5. 原型式继承

伪代码:

var child = Object.create(parent)
复制代码

6. 寄生式继承

伪代码:

function createAnother (original) {
    var clone = Object.create(original);; // 通过调用 Object.create() 函数创建一个新对象
    clone.fn = function () {}; // 以某种方式来增强对象
    return clone; // 返回这个对象
}
复制代码

7. 混入方式继承

伪代码:

function Child () {
    Parent.call(this)
    OtherParent.call(this)
}
Child.prototype = Object.create(Parent.prototype)
Object.assign(Child.prototype, OtherParent.prototype)
Child.prototype.constructor = Child
复制代码

思维导图:

8. class中的继承

伪代码:

class Child extends Parent {
    constructor (...args) {
        super(...args)
    }
}
复制代码

后语

知识无价,支持原创。

参考文章:

你盼世界,我盼望你无bug。这篇文章就介绍到这里。

其实实现继承的方式真的有好多种啊~

我在写之前还考虑要不要把这些情况都写进去,因为那样题目势必会很多。

但是后来我反思了一下自己

"啪!"

"我提莫在想什么?"

霖呆呆我出这些题不就是为了难为你嘛,那我还在顾虑什么~

另外细心的小伙伴数了数总题数,这也就只有31道啊,哪来的48道题。

(我把《封装篇》里的那17道也算进来了,怎么滴...你又不是不知道霖呆呆我是标题党)

现在将题目全部弄懂之后是不是对面向对象以及原型链更加熟悉了呢 😁。

没点赞的小伙伴还请给波赞哦👍,你的每个赞对我都很重要 😊。
作者:LinDaiDai_霖呆呆
链接:https://juejin.im/post/5e75e22951882549027687f9
来源:掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

— 于 共写了35941个字
— 文内使用到的标签:

发表评论

电子邮件地址不会被公开。 必填项已用*标注