js——继承
继承概念
继承就是子类可以使用父类的所有功能,并且对这些功能进行扩展。
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
上。
1.2
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)
//undifined
//Child{name:child}
分析:
child1
上能使用的属性和方法只有name、getSex
,所以getSex
打印出的会是undefined
- 打印出的
child1
只有name
属性,getSex
为原型上的方法所以并不会表现出来。
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{sex:girl,feature:['cute','sunshine']}
//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()
这样的语句后面 - 无法实现多继承(因为已经指定了原型对象了)
- 来自原型对象的所有属性都被共享了,这样如果不小心修改了原型对象中的引用类型属性,那么所有子类创建的实例对象都会受到影响
- 创建子类时,无法向父类构造函数传参数
2.instanceof
instanceof
运算符用于检测构造函数的 prototype
属性是否出现在某个实例对象的原型链上。
2.1
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
分析:
2.2
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
3.构造继承
了解了最简单的原型链继承,再让我们来看看构造继承呀,也叫做构造函数继承。
在子类构造函数内部使用call或apply
来调用父类构造函数
- 通过
call()、apply()
或者bind()
方法直接指定this
的绑定对象, 如foo.call(obj)
- 使用
.call()
或者.apply()
的函数是会直接执行的 - 而
bind()
是创建一个新的函数,需要手动调用才会执行 .call()
和.apply()
用法基本类似,不过call
接收若干个参数,而apply
接收的是一个数组
3.1
function Parent (name) {
this.name = name
}
function Child () {
this.sex = 'boy'
Parent.call(this, 'child')
}
var child1 = new Child()
console.log(child1)
//Child{sex:'boy',name:'child'}
3.2
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)
//Child{sex:'boy',name:'bad 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)
//Child{name:'child1',sex:'boy',colors:['white', 'black','yellow']}
//Child{name:'child2',sex:'girl',colors:['white', '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
分析:
构造继承只能继承父类的实例属性和方法,不能继承父类原型的属性和方法
3.5
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
总结-构造继承
优点:
- 解决了原型链继承中子类实例共享父类引用对象的问题,实现多继承,创建子类实例时,可以向父类传递参数(见题目
3.3
)
缺点:
- 构造继承只能继承父类的实例属性和方法,不能继承父类原型的属性和方法(见题目
3.4
) - 实例并不是父类的实例,只是子类的实例(见题目
3.5
) - 无法实现函数复用,每个子类都有父类实例函数的副本,影响性能
4.组合继承
概念:
组合继承就是将原型链继承与构造函数继承组合在一起,从而发挥两者之长的一种继承模式。
思路:
- 使用原型链继承来保证子类能继承到父类原型中的属性和方法
- 使用构造继承来保证子类能继承到父类的实例属性和方法
基操:
- 通过
call/apply
在子类构造函数内部调用父类构造函数 - 将子类构造函数的原型对象指向父类构造函数创建的一个匿名实例
- 修正子类构造函数原型对象的
constructor
属性,将它指向子类构造函数
4.1
实现这么一个Child
和Parent
构造函数(代码尽可能地少),让它们代码的执行结果能如下:
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 Child(name) {
Parent.call(this, name);
this.sex = 'boy';
}
function Parent(name) {
this.name = name;
}
Child.prototype = new Parent;
Parent.prototype.getName = function () {
console.log(this.name);
}
Child.prototype.getSex = function () {
console.log(this.sex);
}
分析:
首先来看看俩构造函数产生的实例(child1和parent1
)上都有name
这个属性,所以name
属性肯定是在父类的构造函数里定义的啦,而且是通过传递参数进去的。
其次,sex
属性只有实例child1
才有,表明它是子类构造函数上的定义的属性(也就是我们之前提到过的公有属性)
再然后child1
和parent1
都可以调用getName
方法,并且都没有表现在实例上,所以它们可能是在Parent.prototype
上。
而getSex
对于child1
是可以调用的,对于father1
是不可调用的,说明它是在Child.prototype
上。
4.2
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 () {}
分析:
constructor
它存在的位置:
获取child1.constructor
,肯定是向上查找,通过__proto__
找它构造函数的原型对象匿名实例
。
但是匿名实例它自身是没有constructor
属性的呀,它只是Parent
构造函数创建出来的一个对象而已,所以它也会继续向上查找,然后就找到了Parent
原型对象上的constructor
,也就是Parent
constructor
它是构造函数原型对象中的一个属性,正常情况下它指向的是原型对象。
它并不会影响任何JS
内部属性,只是用来标示一下某个实例是由哪个构造函数产生的而已。
如果我们使用了原型链继承或者组合继承无意间修改了constructor
的指向,那么出于编程习惯,我们最好将它修改为正确的构造函数。
4.3
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
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
分析:
- 两个
child
的sex
和name
都没啥问题,而colors
可能会有些疑问,因为colors
是通过构造继承于父类的,并且是复制出来的属性,所以改变child1.colors
并不会影响child2.colors
。 - 而
Child.prototype
,是使用new Parent
生成的,并且生成的时候是没有传递参数进去的,因此name
和colors
都是undefined
。而且题目中又将constructor
给修正指向了Child
。 - 最后两个
true
,是因为child1
可以沿着它的原型链查找到Child.prototype
和Parent.prototype
。
组合继承的优点
- 可以继承父类实例属性和方法,也能够继承父类原型属性和方法
- 弥补了原型链继承中引用属性共享的问题
- 可传参,可复用
4.5
function Parent (name) {
console.log(name)
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'}
分析:
组合继承的缺点:
- 使用组合继承时,父类构造函数会被调用两次
- 并且生成了两个实例,子类实例中的属性和方法会覆盖子类原型(父类实例)上的属性和方法,所以增加了不必要的内存。
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']
分析:
colors
属性虽然定义在Parent
构造函数中,但是Child
通过构造继承复制了其中的属性,所以它存在于各个实例当中,改变child1
里的colors
就不会影响其它地方了
features
是定义在父类构造函数原型对象中的,是比new Parent()
还要更深一层的对象,在child
实例还有Child.prototype
(也就是new Parent()
产生出了的匿名实例)上都没有features
属性,因此它们只能去它们共有的Parent.prototype
上面拿了,所以这时候它们就是共用了一个features
,因此改变child1.features
就会改变child2.features
了。
总结-组合继承
实现方式:
- 使用原型链继承来保证子类能继承到父类原型中的属性和方法
- 使用构造继承来保证子类能继承到父类的实例属性和方法
优点:
- 可以继承父类实例属性和方法,也能够继承父类原型属性和方法
- 弥补了原型链继承中引用属性共享的问题
- 可传参,可复用
缺点:
- 使用组合继承时,父类构造函数会被调用两次
- 并且生成了两个实例,子类实例中的属性和方法会覆盖子类原型(父类实例)上的属性和方法,所以增加了不必要的内存。
constructor总结:
constructor
它是构造函数原型对象中的一个属性,正常情况下它指向的是原型对象。- 它并不会影响任何
JS
内部属性,只是用来标示一下某个实例是由哪个构造函数产生的而已。 - 如果我们使用了原型链继承或者组合继承无意间修改了
constructor
的指向,那么出于编程习惯,我们最好将它修改为正确的构造函数。
5.寄生组合继承
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{sex:"boy",name:'child1'}
//'child1'
//Parent{}
//{}
//{}
分析:
- 使用寄生组合继承,
child1
不仅仅有自己的实例属性sex
,而且还复制了父类中的属性name
- 寄生组合继承使得实例
child1
能通过原型链查找,使用到Parent.prototype
上的方法,因此打印出child1
。
child1.__proto__
也就是Child.prototype
,也就是Object.create(Parent.prototype)
,这个空对象它的__proto__
指向的就是我们想要的父类的原型对象,所以child1
就能使用Parent.prototype
上的方法了。
而通过Object.create(null)
创建的对象呢?哇,这可真的是空的不能再空了,因为我们创建它的时候传递的参数是null
,也就是将它的__proto__
属性设置为null
,那它就相当于是没有原型链了,连Object.prototype
上的方法它都不能用了(比如toString()、hasOwnProperty()
)
再来看看new Object()
,这个其实很好理解了,Object
本身就是一个构造函数,就像Parent、Child
这种,只不过它的原型对象是我们常用的Object.prototype
。
5.2
//Child{name:'child1',face:'smile',colors:['white','black','yellow']}
//Child{name:'child2',face:'smile',colors:['white','black'],features:['sunshine']}
//['cute']
//['sunshine']
分析:
name、face、sex
三个属性都没有啥问题,要注意的只是face
属性,后面写的会覆盖前面的
colors
属性是通过构造继承复制过来的,所以改变child1.colors
对其他实例没有影响,这个说过很多次了。
要注意的就是这里的features
,在没有执行child2.features = ['sunshine']
这段代码之前,child1
和child2
都是共用原型链上的features
,但是执行了这段代码之后,就相当于是给child2
对象上新增了一个名为features
属性,所以这时候child2
取的就是它自身的了。
总结-寄生组合继承
避免了组合继承中调用两次父类构造函数,初始化两次实例属性的缺点。
所以它拥有了上述所有继承方式的优点:
- 只调用了一次父类构造函数,只创建了一份父类属性
- 子类可以用到父类原型链上的属性和方法
- 能够正常的使用
instanceOf
和isPrototypeOf
方法
6.原型式继承
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']
6.2
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)
总结-原型式继承
实现方式:
该方法的原理是创建一个构造函数,构造函数的原型指向对象,然后调用 new 操作符创建实例,并返回这个实例,本质是一个浅拷贝。
在ES5
之后可以直接使用Object.create()
方法来实现,而在这之前只能手动实现一个
优点:
- 在不用创建构造函数的情况下,实现了原型链继承,代码量减少一部分。
缺点:
- 一些引用数据操作的时候会出问题,两个实例会公用继承实例的引用数据类
- 谨慎定义方法,以免定义方法也继承对象原型的方法重名
- 无法直接给父级构造函数使用参数
7.寄生式继承
寄生式继承是在原型式继承的基础上再封装一层,来增强对象,之后将这个对象返回。
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:f}
//{}
分析:
guaiguai
调用actingCute()
会打印卖萌
两只猫都是通过Object.create()
进行过原型式继承cat对象的,所以是共享使用
cat对象中的属性
guaiguai
经过createAnother
新增了自身的实例方法actingCute
,所以会有这个方法
huaihuai
是空,因为heart、colors
都是原型对象cat
上的属性
总结-寄生式继承
实现方式:
- 在原型式继承的基础上再封装一层,来增强对象,之后将这个对象返回。
优点:
- 再不用创建构造函数的情况下,实现了原型链继承,代码量减少一部分。
缺点:
- 一些引用数据操作的时候会出问题,两个实例会公用继承实例的引用数据类
- 谨慎定义方法,以免定义方法也继承对象原型的方法重名
- 无法直接给父级构造函数使用参数
8.混入方式继承多个对象
混入方式继承是一个子类继承多个父类。
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)
//'boy'
//['white']
//Child{sex:'boy',colors:['white'],name:'child'}
分析:
Object.assign()
的作用是可以把多个对象的属性和方法拷贝到目标对象中,若是存在同名属性的话,后面的会覆盖前面。
现在的child1
不仅复制了Parent
上的属性和方法,还复制了OtherParent
上的。
而且它不仅可以使用Parent.prototype
的属性和方法,还能使用OtherParent.prototype
上的。
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)
//true
//false
//true
//false
分析:
Child
内使用了call/apply
来复制构造函数OtherParent
上的属性和方法Child.prototype
使用Object.assign()
浅拷贝OtherParent.prototype
上的属性和方法
9.class中继承
class中继承依赖于extends、super
9.1
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
9.2
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()
//Child{name:'child1',sex:'boy'}
//child1
分析:
在class
中如果没有定义constructor
方法的话,这个方法是会被默认添加的,那么这里我们没有使用constructor
,它其实已经被隐式的添加和调用了。
extends的作用:
class
可以通过extends
关键字实现继承父类的所有属性和方法- 若是使用了
extends
实现继承的子类内部没有constructor
方法,则会被默认添加constructor
和super
。
9.3
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
ES6的继承机制:
ES5
中的继承(例如构造继承、寄生组合继承) ,实质上是先创造子类的实例对象this
,然后再将父类的属性和方法添加到this
上(使用的是Parent.call(this)
)。- 而在
ES6
中却不是这样的,它实质是先创造父类的实例对象this
(也就是使用super()
),然后再用子类的构造函数去修改this
。
9.4
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)
//'Child'
//Child{}
//true
//'Parent'
//Child{}
//Parent{}
分析:
new.target
属性允许你检测函数或构造方法是否是通过new运算符被调用的。在通过new运算符被初始化的函数或构造方法中,new.target
返回一个指向构造方法或函数的引用。在普通的函数调用中,new.target
的值是[undefined
]
super
当成函数调用时,代表父类的构造函数,且返回的是子类的实例,也就是此时super
内部的this
指向子类。- 在子类的
constructor
中super()
就相当于是Parent.constructor.call(this)
9.5
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)
//Uncaught ReferenceError: Must call super constructor in derived class before accessing 'this' or returning from derived constructorat new Child
分析:
super
的作用:在constructor
中必须得有super()
,它就是用来产生实例this
的,那么再调用它之前,访问不到this
。
也就是在this.sex = 'boy'
这一步的时候就已经报错。
9.6
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)
//child1
//boy
//['white']
//Child{name:'child1'}
分析:
-
在子类的普通函数中
super
对象指向父类的原型对象 -
在子类的静态方法中
super
对象指向父类 -
在使用
new Child('child1')
创建child1
的时候,会执行子类constructor
中的方法,因此会执行super.getName()
,而依靠准则一,此时的constructor
中的第二个super
指向的是父类的原型对象,因此此时super.getName()
会被成功调用,并打印出'child1'
。(第一个super
是当成函数来调用) -
当
child1
创建完之后,执行了child1.instanceFn()
,这时候依据准则一,instanceFn
函数中的super
指向的还是父类的原型对象,因此super.getSex()
也会被成功调用,并打印出'boy'
。 -
staticFn
属于子类的静态方法,所以需要使用Child.staticFn()
来调用,且依据准则二,此时staticFn
中的super
指向的是父类,也就是Parent
这个类,因此调用其静态方法getColors
成立,打印出['white']
。 -
最后需要打印出
child1
,我们只需要知道哪些是child1
的实例属性和方法就可以了,通过比较很容易就发现,child1
中就只有一个name
属性是通过调用super(name)
从父级那里复制来的,其它方法都不能被child1
"表现"出来,但是可以调用。
9.7
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)
//girl
//Child{sex:'girl'}
分析:
ES6
规定,通过super
调用父类的方法时,super
会绑定子类的this
。
super
其实还有一个特性,在使用时,必须得显式的指定它是作为函数使用还是对象来使用,否则会报错
9.8
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]
分析:
- 可以继承构造函数
Parent
- 不存在任何继承,就是一个普通的函数,所以直接继承
Function.prototype
- 可以继承原生构造函数
总结-class继承
ES6中的继承:
- 主要是依赖
extends
关键字来实现继承,且继承的效果类似于寄生组合继承 - 使用了
extends
实现继承不一定要constructor
和super
,因为没有的话会默认产生并调用它们 extends
后面接着的目标不一定是class
,只要是个有prototype
属性的函数就可以了
super相关:
- 在实现继承时,如果子类中有
constructor
函数,必须得在constructor
中调用一下super
函数,因为它就是用来产生实例this
的。 super
有两种调用方式:当成函数调用和当成对象来调用。super
当成函数调用时,代表父类的构造函数,且返回的是子类的实例,也就是此时super
内部的this
指向子类。在子类的constructor
中super()
就相当于是Parent.constructor.call(this)
。super
当成对象调用时,普通函数中super
对象指向父类的原型对象,静态函数中指向父类。且通过super
调用父类的方法时,super
会绑定子类的this
,就相当于是Parent.prototype.fn.call(this)
。
ES5继承和ES6继承的区别:
- 在
ES5
中的继承(例如构造继承、寄生组合继承) ,实质上是先创造子类的实例对象this
,然后再将父类的属性和方法添加到this
上(使用的是Parent.call(this)
)。 - 而在
ES6
中却不是这样的,它实质是先创造父类的实例对象this
(也就是使用super()
),然后再用子类的构造函数去修改this
。
知识无价,支持原创。
作者:LinDaiDai_霖呆呆
原文链接
此处仅做记录,用于学习。