做完这48道题彻底弄懂JS继承|博客系列
# JavaScript对象封装、多态、继承
# 继承
继承就是子类可以使用父类的所有功能,并且对这些功能进行扩展。
比如我有个构造函数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"}
这…这很好理解呀
child1是通过子类构造函数Child生成的对象,那我就有属性name,并且属性值也是自己的child- 然后子类构造函数
Child它的原型被指向了父类构造函数Parent创建出来的"无名实例" - 这样的话,我
child1就可以使用你这个"无名实例"里的所有属性和方法了呀,因此child1.getName()有效。并且打印出child。 - 另外由于
sex、getName都是Child原型对象上的属性,所以并不会表现在child1上。

所以现在你知道了吧,这种方式就叫做原型链继承。
将子类的原型对象指向父类的实例。
我们来写个伪代码,方便记忆:
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它的原型链现在长啥样了。
解析:
child1上能使用的属性和方法只有name、getSex,所以getSex打印出的会是undefined- 打印出的
child1只有name属性,getSex为原型上的方法所以并不会表现出来。
这道题是个错误的做法啊 😂
我只是为了说明一下,为什么原型链继承是要用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'] }
解析:
child1在创建完之后,就设置了sex,并且给colors和feature都push了新的内容。child1.sex = 'girl'这段代码相当于是给child1这个实例对象新增了一个sex属性。相当于是:原本我是没有sex这个属性的,我想要获取就得拿原型对象parent上的sex,但是现在你加了一句child1.sex就等于是我自己也有了这个属性了,就不需要你原型上的了,所以并不会影响到原型对象parent上😊。- 但是
child1.colors这里,注意它的操作,它是直接使用了.push()的,也就是说我得先找到colors这个属性,发现实例对象parent上有,然后就拿来用了,之后执行push操作,所以这时候改变的是原型对象parent上的属性,会影响到后续所有的实例对象。(这里你会有疑问了,凭什么sex就是在实例对象child上新增,而我colors不行,那是因为操作的方式不同,sex那里是我不管你有没有,反正我就直接用=来覆盖你了,可是push它的前提是我得先有colors且类型是数组才行,不然你换成没有的属性,比如一个名为clothes的属性,child1.clothes.push('jacket')它直接就报错了,如果你使用的是child1.colors = ['yellow']这样才不会影响parent) - 而
feature它是属于child1实例自身的属性,它添加还是减少都不会影响到其他实例。 - 因此
child1打印出了feature和sex两个属性。(name和colors属于原型对象上的属性并不会被表现出来) child2没有做任何操作,所以它打印出的还是它自身的一个feature属性😁。child1.name是原型对象parent上的name,也就是'parent',虽然我们在new Child的时候传递了'child1',但它显然是无效的,因为接收name属性的是构造函数Parent,而不是Child。child2.colors由于用的也是原型对象parent上的colors,又由于之前被child1给改变了,所以打印出来的会是['white', 'black', 'yellow']- 将最后的原型对象
parent打印出来,name和sex没变,colors却变了。
分析的真漂亮,漂亮的这么一大串我都不想看了…
咳咳,不过你要是能静下来认真的读一读的话就会觉得真没啥东西,甚至不需要记什么,我就理解了。
# 总结-原型链继承
现在我们就可以得出原型链继承它的优点和缺点了
优点:
- 继承了父类的模板,又继承了父类的原型对象
缺点:
- 如果要给子类的原型上新增属性和方法,就必须放在
Child.prototype = new Parent()这样的语句后面 - 无法实现多继承(因为已经指定了原型对象了)
- 来自原型对象的所有属性都被共享了,这样如果不小心修改了原型对象中的引用类型属性,那么所有子类创建的实例对象都会受到影响(这点从修改
child1.colors可以看出来) - 创建子类时,无法向父类构造函数传参数(这点从
child1.name可以看出来)
这…这看到没,压根就不需要记,想想出的这道变态的题面试的时候被问到脱口就来了。
# 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相反。
