相关知识点
- 原型链继承
- 构造函数继承
- 组合继承
- 寄生组合继承
- 原型式继承
- 寄生式继承
- class 中的继承
上一期说到构造函数继承 JS中的继承-1【总结】,接下来我们继续总结~~
组合继承
组合继承的概念
组合继承就是将原型链继承与构造函数继承组合在一起,从而发挥两者之长的一种继承模式。
// 原型链继承
Child.prototype = new Parent();
// 构造继承
function Child () {
Parent.call(this, ...arguments);
}
思路
- 使用原型链继承来保证子类能继承到父类原型中的属性和方法;
- 使用构造函数继承来保证子类能继承父类的实例属性和方法;
基本操作
- 通过
call/apply
在子类构造函数内部调用父类构造函数(构造函数继承); - 将子类构造函数的原型对象指向父类构造函数创建的一个匿名实例(原型链继承);
- 修正子类构造函数原型对象的
constructor
属性,将它指向子类构造函数;
题目一
理解 constructor
有的作用
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); // f Parent () {}
console.log(parent1.constructor); // f Parent () {}
parent1.constructor
是 Parent
函数这个还好理解,只要通过原型链查找,parent1
实例自身没有 constructor
属性,那么就顺着向上找,拿原型上的 constructor
,发现它指向的是构造函数 Parnent
,因此第二个打印出 Parent
函数。
而对于 child1
,原型链继承切断了原本 Child
和 Child
原型对象的关系,而是重新指向了匿名实例。使得实例 child1
能够使用匿名实例原型链上的属性和方法。
当我们想要获取 child1.constructor
,肯定是向上查找,通过 __proto__
找它构造函数的原型对象匿名实例,但是匿名实例它自身是没有 constructor
属性,它只是 Parent
构造函数创建出来的一个对象而已,所以它也会继续向上查找,然后就找到了 Parent
原型对象上的 constructor
,也就是 Parent
了。
所以:constructor
它不过是给我们一个提示,用来标示实例对象是由哪个构造函数创建的。
从常理来讲,child1
是 Child
构建的,parent1
是Parent
构建的。
那么 child1
它的 constructor
就应该是 Child
,但是现在却变成了 Parent
,貌似并不太符合常理。
所以才有了这么一句:
Child.prototype.constructor = Child;
用以修复 constructor
的指向。
总结来说:
constructor
它是构造函数原型对象中的一个属性,正常情况下它指向的是原型对象;- 它并不会影响任何 JS 内部属性,只是用来标示一下某个实例是由哪个构造函数产生的而已;
- 如果我们使用了原型链继承或者组合继承无意间修改了
constructor
指向,那么出于编程的习惯,我们最好将它修正为正确的构造函数;
题目二
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(); // => a.a => 1
- 定义了一个全局的变量
a
,和一个构造函数A
; - 在立即执行函数中,是可以访问到全局变量
a
的,因此a
被赋值为了一个构造函数A
生成的对象; - 并且
a
对象中有两个属性:a
和b
; - 之后在外层调用
a.logA()
,打印出的就是a.a
,也就是1
;
问题:如果要在匿名函数外给 A
这个构造函数的原型对象中添加一个方法 logB
用以打印出 this.b
。
解决思路:虽然我们在外层访问不到 A
,但是我们可以通过原型链查找,来获取 A
的原型对象。
方法1:通过 a.__proto__
来访问到原型对象
a.__proto__.logB = function () {
console.log(this.b);
};
a.logB();
方法2:通过 a.constructor.prototype
来访问到原型对象
a.constructor.prototype.logB = function () {
console.log(this.b);
};
a.logB();
虽然 a
实例上没有 constructor
,但是原型对象上有,所以 a.constructor
实际上拿的是原型对象上的 constructor
。
题目三
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); // Child{ sex: "boy", name: "child1", colors: ["white", "yellow"] }
console.log(child2); // Child{ sex: "boy", name: "child2", colors: ["black"] }
console.log(Child.prototype); // Parent{ name: undefined, colors: undefined, constructor: f Child () {} }
console.log(child1 instanceof Child); // true
console.log(child1 instanceof Parent); // true
child1
和 child2
的 sex
和 name
都有值,分别为 boy
和 child1/child2
,这个没有什么疑问,而 colors
可能有一些疑惑,因为 colors
是通过构造继承与父类的,并且是复制出来的属性,所以改变 child1.colors
并不会影响 child2.colors
。
而 Child.prototype
是使用 new Parent
生成的,并且生成的时候是没有传到参数进去的,因此 name
和 colors
都是 undefined
。
最后两个为 true
,是因为 child1
可以沿着它的原型链查找到 Child.prototype
和 Parent.prototype
。
现在可以看出组合继承的优点,它其实就是将两种继承方式的优点结合了起来:
- 可以继承父类实例属性和方法,也可以继承父类原型属性和方法;
- 弥补了原型链继承中引用属性共享的问题;
- 可传参、可复用;
题目四
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 }
我们虽然只调用了 new Child()
一次,但是在 Parent
中却打印了两次 name
.
- 第一次是原型链继承的时候,
new Parent()
; - 第二次是构造继承的时候,
Parent.call()
调用的;
也就是说,在使用组合继承的时候,会多余的调用一次父类的构造函数。
另外,我们想要继承父类构造函数里面的属性和方法采用的是构造继承,也就是复制一份到子类实例对象中,而此时由于调用了 new Parent()
,所以 Child.prototype
中也会有一份一模一样的属性,就例如这里的 name: undefined
,可是子类实例对象自己已经有一份了,所以就用不上 Child.prototype
上的了,那么就会造成内存浪费。
因此我们可以看出组合继承的缺点:
- 使用组合继承时,父类构造函数会被调用两次;
- 生成了两个实例,子类实例中的属性和方法会覆盖子类原型(父类实例)上的属性和方法,所以增加了不必要的内存。
总结-组合继承
实现方式:
- 使用原型链继承来保证子类能够继承到父类原型中的属性和方法;
- 使用构造继承来保证子类能够继承到父类的实例属性和方法;
优点:
- 可以继承父类实例的属性和方法,也能够继承父类原型的属性和方法;
- 弥补了原型链继承中引用属性共享的问题;
- 可传参、可复用
缺点:
- 使用组合继承时,父类构造函数会被调用两次;
- 生成了两个实例,子类实例中的属性和方法会覆盖子类原型(父类实例)上的属性和方法,所以增加了不必要的内存。
constructor 总结;
constructor
它是构造函数原型对象中的一个属性,正常情况下它指向的是原型对象的构造器的而引用;- 它并不会影响任何 JS 内部属性,只是用来标示一下某个实例是由哪个构造函数产生的而已;
- 如果使用了原型链继承或者组合继承无意间修改了
constructor
的指向,那么出于编程习惯,我们最好将它修改为正确的构造函数;
寄生组合继承【重点】
组合继承的缺点是:
- 父类构造函数会被调用两次;
- 生成了两个实例,在父类实例上产生了无用废弃的属性;
需要一个干净的实例对象,来作为子类的原型。并且这个干净的实例对象还得能继承父类原型对象里的属性。
可以使用:Object.create();
用法:Object.create(proto, propertiesObject);
- 参数一:需要指定的原型对象
- 参数
proto
:作用就是能指定你要新建的这个对象它的原型对象是谁
- 参数
- 参数二:可选参数,给新对象自身添加新属性以及描述器
题目一
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); // Child {sex: "boy", name: "child1"}
child1.getName(); // child1
console.log(child1.__proto__); // Parent {}
上面就是一个标准的寄生组合继承,它与*组合继承的区别仅仅是 Child.prototype
不同。
- 使用寄生组合继承,
child1
不仅仅有自己的实例属性sex
,而且还复制了父类中的属性name
; - 寄生组合继承使得实例
child1
能通过原型链查找,使用到Parent.prototype
上的方法,因此打印出child1
;
我们使用了 Object.create(Parent.prototype)
创建了一个空的对象,并且这个对象的 __proto__
属性是指向 Parent.prototype
的。
现在 Parent()
已经和 child
没有关系了,仅仅是用了 Parent.call(this, xxx)
来复制 Parent
里面的属性和方法。
题目二
function Parent(name) {
this.name = name;
this.face = "cry";
this.colors = ["white", "black"];
}
Parent.prototype.features = ["cute"];
Parent.prototype.getFeatures = function () {
console.log(this.features);
};
function Child(name) {
Parent.call(this, name);
this.sex = "boy";
this.face = "smile";
}
Child.prototype = Object.create(Parent.prototype);
Child.prototype.constructor = Child;
var child1 = new Child("child1");
child1.colors.push("yellow");
var child2 = new Child("child2");
child2.features = ["sunshine"];
console.log(child1); // Child{ name: 'child1', face: 'smile', sex: 'boy', colors: ['white', 'black', 'yellow'] }
console.log(child2); // Child{ name: 'child2', face: 'smile', sex: 'boy', colors: ['white', 'black'], features: ['sunshine'] }
child1.getFeatures(); // ["cute"]
child2.getFeatures(); // ["sunshine"]
name
、face
、sex
三个属性都没有啥问题,要注意的只是face
属性,后面写的会覆盖前面的;colors
属性是通过构造继承复制过来的,所以改变child1.colors
对其他实例没有影响;- 要注意的就是这里的
features
,在没有执行child2.features = ['sunshine']
这段代码之前,child1
和child2
都是共用原型链上的features
,但是执行了这段代码之后,就相当于是给child2
对象上新增了一个名为features
属性,所以这时候child2
取的就是它自身的了;
总结-寄生组合继承
寄生组合继承算是 ES6 之前一种比较完美的继承方法。
它避免了组合继承中调用两次父类构造函数,初始化两次实例属性的缺点。
所以它拥有了所有继承方式的优点:
- 只调用了一次父类构造函数,只创建了一份父类属性;
- 子类能够访问到父类原型链上的属性和方法;
- 能够正常的使用
instanceOf
和isPrototypeOf
方法;
原型式继承
该方法的原理是创建一个构造函数,构造函数的原型指向对象,然后调用 new
操作符创建实例,并返回这个实例,本质是一个浅拷贝。
伪代码如下:
function objcet (obj) {
function F () {};
F.prototype = obj;
F.prototype.constructor = F;
return new F();
}
题目一
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
的空对象。
Object.create()
的作用:
- 它接受的是一个对象;
- 返回的是一个新对象;
- 新对象的原型链中必须能找到传进来的对象;
总结-原型式继承
实现方式:
该方法的原理是创建一个构造函数,构造函数的原型指向对象,然后调用 new 操作符创建实例,并返回这个实例,本质是一个浅拷贝。
优点:
再不用创建构造函数的情况下,实现了原型链继承,代码量减少一部分。
缺点:
- 一些引用数据操作的时候会出问题,两个实例会公用继承实例的引用数据类;
- 谨慎定义方法,以免定义方法也继承对象原型的方法重名;
- 无法直接给父级构造函数使用参数;
寄生式继承
寄生式继承就是在原型式继承的基础上再封装一层,来增强对象,之后将这个对象返回。
伪代码如下:
function createAnother (original) {
var clone = Object.create(original); // 通过调用 Object.create() 函数创建一个新对象
clone.fn = function () {}; // 以某种方式来增强对象
return clone; // 返回这个对象
}
题目一
例如我现在想要继承某个对象上的属性,同时又想在新创建的对象中新增上一些其它的属性。
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); // { actingCute: ƒ }
console.log(huaihuai); // {}
console.log(guaiguai.heart); // 心
console.log(guaiguai.colors); // ["white", "black"]
guaiguai
是一直经过加工的小猫咪,所以它会卖萌,因此调用actingCute()
会打印卖萌;- 两只猫都是通过
Object.create()
进行过原型式继承cat
对象的,所以是共享使用cat
对象中的属性; guaiguai
经过createAnother
新增了自身的实例方法actingCute
,所以会有这个方法huaihuai
是一只空猫,因为heart、colors
都是原型对象cat
上的属性;
总结-寄生式继承
实现方式:
在原型式继承的基础上再封装一层,来增强对象,之后将这个对象返回。
优点:
再不用创建构造函数的情况下,实现了原型链继承,代码量减少一部分。
缺点:
- 一些引用数据操作的时候会出问题,两个实例会公用继承实例的引用数据类;
- 谨慎定义方法,以免定义方法也继承对象原型的方法重名;
- 无法直接给父级构造函数使用参数;
class 中的继承【重点】
在 class
中继承主要是依靠两个东西:
extends
super
而且对于该继承的效果和之前我们介绍过的寄生组合继承方式一样。
题目一
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); // Child {name: "child1", sex: "boy"}
child1.getName(); // child1
console.log(child1 instanceof Child); // true
console.log(child1 instanceof Parent); // 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); // Child {sex: "boy", name: "child1"}
child1.getName(); // child1
console.log(child1 instanceof Child); // true
console.log(child1 instanceof Parent); // true
class
继承和寄生组合继承相对比,可以看到 class
的继承方式完全满足于寄生组合继承。
题目二
可以看到上面那道题,我们用到了两个关键的东西:extends
和 super
。
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)
}
}
在 class
中如果没有定义 constructor
方法的话,这个方法是会被默认添加的,那么这里我们没有使用 constructor
,它其实已经被隐式的添加和调用了。
所以我们可以看出 extends
的作用:
class
可以通过extends
关键字实现继承父类的所有属性和方法;- 若是使用了
extends
实现继承的子类内部没有constructor
方法,则会被默认添加constructor
和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);
// Uncaught ReferenceError: Must call super constructor in derived class before accessing 'this' or returning from derived constructor at new Child
- 在
ES5
中的继承(例如构造继承、寄生组合继承),实质上是先创造子类的实例对象this
,然后再将父类的属性和方法添加到this
上(使用的是Parent.call(this)
); - 而在
ES6
中却不是这样的,它实质是先创造父类的实例对象this
(也就是使用super()
),然后再用子类的构造函数去修改this
;
通俗理解就是,子类必须得在 constructor
中调用 super
方法,否则新建实例就会报错,因为子类自己没有自己的 this
对象,而是继承父类的 this
对象,然后对其加工,如果不调用 super
的话子类就得不到 this
对象。
题目四
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(this);
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{}
Child{}
true
'Parent'
Child{}
Parent{}
new.target
代表的是new
后面的那个函数,那么new.target.name
表示的是这个函数名,所以在执行new Child
的时候,由于调用了super()
,所以相当于执行了Parent
中的构造函数,因此打印出了'Child'
;- 另外,关于
super()
的返回值instance
,它返回的是子类的实例,因此instance
会打印出Child{}
;并且instance
和子类construtor
中的this
相同,所以打印出true
; - 而执行
new Parent
的时候,new.target.name
打印出的就是'Parent'
了;
通过这道题可以看出:
super
当成函数调用时,代表父类的构造函数,且返回的是子类的实例,也就是此时super
内部的this
指向子类;- 在子类的
constructor
中super()
就相当于是Parent.constructor.call(this)
;
题目五
已经说明了 super
当成函数调用的时候就相当于是用 call
来改变了父类构造函数中的 this
指向,那么它的使用有什么限制呢?
- 子类
constructor
中如果要使用this
的话就必须放到super()
之后; super
当成函数调用时只能在子类的construtor
中使用;
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 constructor at new Child
这也就符合了刚刚说到的第一点:子类 constructor
中如果要使用 this
的话就必须放到 super()
之后。
这点其实非常好理解,还记得 super
的作用吗?在 constructor
中必须得有 super()
,它就是用来产生实例this
的,那么再调用它之前,肯定是访问不到 this
的。
题目六
super
当成对象来使用时:
- 在子类的普通函数中
super
对象指向父类的原型对象; - 在子类的静态方法中
super
对象指向父类;
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);
- 在使用
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
"表现"出来,但是可以调用;
所以执行结果为:
'child1'
'boy'
['white']
Child{ name: 'child1' }
题目七
super
当成对象调用父类方法时 this
的指向:
既然 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' }
ES6
规定,通过 super
调用父类的方法时,super
会绑定子类的 this
。
super.getSex.call(this)
// 即
Parent.prototype.getSex.call(this)
而且 super
其实还有一个特性,就是你在使用它的时候,必须得显式的指定它是作为函数使用还是对象来使用,否则会报错的。
比如下面这样就不可以:
class Child extends Parent {
constructor () {
super() // 不报错
super.getSex() // 不报错
console.log(super) // 这里会报错
}
}
题目八
了解 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); // Child1 {name: "parent"}
console.log(child2); // Child2 {}
console.log(child3); // 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
;
常见问题FAQ
- 免费下载或者VIP会员专享资源能否直接商用?
- 本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
- 提示下载完但解压或打开不了?
- 找不到素材资源介绍文章里的示例图片?
- 模板不会安装或需要功能定制以及二次开发?
发表评论
还没有评论,快来抢沙发吧!