js——继承

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,并且给colorsfeaturepush了新的内容。

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打印出了featuresex两个属性。(namecolors属于原型对象上的属性并不会被表现出来)

child2没有做任何操作,所以它打印出的还是它自身的一个feature属性。

child1.name是原型对象parent上的name,也就是'parent',虽然我们在new Child的时候传递了'child1',但它显然是无效的,因为接收name属性的是构造函数Parent,而不是Child

​ child2.colors由于用的也是原型对象parent上的colors,又由于之前被child1给改变了,所以打印出来的会是[‘white’, ‘black’, ‘yellow’]

​ 将最后的原型对象parent打印出来,namesex没变,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

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

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才有,表明它是子类构造函数上的定义的属性(也就是我们之前提到过的公有属性)

​ 再然后child1parent1都可以调用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

分析:

  • 两个childsexname都没啥问题,而colors可能会有些疑问,因为colors是通过构造继承于父类的,并且是复制出来的属性,所以改变child1.colors并不会影响child2.colors
  • Child.prototype,是使用new Parent生成的,并且生成的时候是没有传递参数进去的,因此namecolors都是undefined。而且题目中又将constructor给修正指向了Child
  • 最后两个true,是因为child1可以沿着它的原型链查找到Child.prototypeParent.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']这段代码之前,child1child2都是共用原型链上的features,但是执行了这段代码之后,就相当于是给child2对象上新增了一个名为features属性,所以这时候child2取的就是它自身的了。

总结-寄生组合继承

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

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

  • 只调用了一次父类构造函数,只创建了一份父类属性
  • 子类可以用到父类原型链上的属性和方法
  • 能够正常的使用instanceOfisPrototypeOf方法

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方法,则会被默认添加constructorsuper

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指向子类。
  • 在子类的constructorsuper()就相当于是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实现继承不一定要constructorsuper,因为没有的话会默认产生并调用它们
  • extends后面接着的目标不一定是class,只要是个有prototype属性的函数就可以了

super相关:

  • 在实现继承时,如果子类中有constructor函数,必须得在constructor中调用一下super函数,因为它就是用来产生实例this的。
  • super有两种调用方式:当成函数调用和当成对象来调用。
  • super当成函数调用时,代表父类的构造函数,且返回的是子类的实例,也就是此时super内部的this指向子类。在子类的constructorsuper()就相当于是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_霖呆呆
原文链接

此处仅做记录,用于学习。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值