可以学到什么?
本文希望你能搞清楚哪些问题呢?
- 继承是什么意思
- 继承的原理
- 实现继承的主要方式有哪些,每一个的具体写法以及优缺点
- extends关键字继承
继承的定义
字面意思,比如孩子继承了父亲或者母亲的基因的类似意思。js中,简单的说继承就是子类可以使用父级的方法属性,并且可以进行扩展。
原理
es5,我们利用js查找属性时会查找他的原型链的特性实现的继承。es6虽然出现了关键字 extends,来实现类的继承,但是最终编译以后的代码也是es5的代码。
es5的这个特性原型链应该会讲到,但是这里我们简单看个代码来测试一下。
Object.prototype.name = '随风行酱'
function Person() {}
const b = new Person()
console.log(b) // Person {}
console.log(Object.getPrototypeOf(b)) // {}
console.log(b.name) // 随风行酱
console.log(Object.getPrototypeOf(Object.getPrototypeOf(b))) // [Object: null prototype] { name: 'panhe' }
我们知道,在Person构造函数上合原型上都没有name这个属性。但是其对应的实例对象 b可以访问到name属性,就是因为js会沿着实例属性的原型链上去查找,找到Object原型上的name属性的原因。
这大概就是es5继承抓住的特性,即它的原理了。
继承的方式
继承的主要几种方式有:
- 原型链继承
- 构造函数继承
- 组合继承
- 原型式继承
- 寄生式继承
- 寄生式组合继承
- es6的extends继承
原型链继承
这种方式就是利用原型链查找的特性,来看下什么是原型链继承
function Person () {
this.colors = ['red', 'green']
}
Person.prototype = {
getColors: function() {
return this.colors
}
}
// child
function Child () {
}
Child.prototype = new Person()
const c = new Child()
console.log(Object.getPrototypeOf(c)) // {colors: ['red', 'green'] }
console.log(c.getColors()) // ['red', 'green']
Child内部以及原型上都没有getColors 和 colors 2个属性。
但是通过把Child的原型指向Person实例对象,通过原型链就能访问到Parent上的属性和方法了。
第一,能够访问Person构造函数内部的属性是由于 new Person() 产生的对象会包含this的属性。
第二,能够访问,Person原型上的属性,是由于原型链查找
缺点: 子类修改父类实例属性,会造成引用类型属性同步修改的问题
(画图)
构造函数继承
构造函数用的不是原型链继承,更多的是使用new内部作用来实现继承。来看下什么是构造函数继承
function Person () {
this.colors = ['red', 'green']
}
Person.prototype = {
getColors: function() {
return this.colors
}
}
function Child () {
Person.call(this, ...arguments)
}
const c = new Child();
console.log(c.colors // ['red', 'green']
console.log(c.getColors()) // undefiend
构造函数继承只继承了父级构造函数类的属性。并没有继承父级原型上的属性。
这种继承原理是:利用new 在创建对象是会生成新的对象,并赋值this到这个对象上的特性。把父级的this搬到了子类上来重新绑定。
由于是一个新对象,所以生成的对象都是全新的,父级的引用类型的值不在公用地址(解决了上面的缺点问题)。
缺点: 不能访问父级原型链上的属性
组合继承
组合继承就是2者的结合,如下:
function Person () {
this.colors = ['red', 'green']
}
Person.prototype.getColors = function() {
return this.colors
}
function Child () {
Person.call(this, ...arguments)
}
Child.prototype = new Person()
可以看出来,2这结合以后,new 一个子类会调用2次父级的构造函数。 其实,就是在原型链基础上覆盖了构造函数内部的属性,来做到不共享引用类型数据的问题。
缺点: 父级构造函数调用2次,并且构造函数内的属性生成了2份。
原型式继承,寄生式继承,寄生组合继承
看图说话:
原型式继承
图的最上面为原型式继承。我们用object函数会生成一个新的以function F为构造函数的对象。生成的对象的__proto__
会指向这个被继承的对象。这样就形成了原型链。
function object(o) {
function F() {}
F.prototype = o
return new F()
}
const cat = {
heart: '@',
colors: ['white', 'black']
}
const child = object(cat)
缺点: 引用类型属性同步修改
寄生式继承
寄生式继承就是在原型继承基础上,可以给生成的对象增加属性。
function object(o) {
function F() {}
F.prototype = o
return new F()
}
function createAnthor(origin) {
const clone = object(origin)
clone.sayhi = function() {
return 'hi'
}
return clone
}
const cat = {
heart: '@',
colors: ['white', 'black']
}
const child = createAnthor(cat)
缺点: 引用类型属性同步修改
寄生式组合继承
从图最下方可以看书,Child的实例child的__proto__
指向了中间代理的对象prototype,然后prototype的__proto__
又指向了Parent,这样就形成了原型链了。
function object(o) {
function F() {}
F.prototype = o
return new F()
}
function createAnthor(Child, Parent) {
const prototype = object(Parent.prototype) // 产生一个对象指向 parent.prototype
prototype.constructor = Child // 每个原型对象有一个constructor对象
Child.prototype = prototype
}
function Person() {}
Person.prototype = {}
function Child() {}
Child.prototype = {}
createAnthor(Child, Person)
extends关键字继承
Es6 的继承是由extends实现继承的。其中向下编译结果原理类似组合继承和寄生式组合继承结合。
来看下编译。
前:
class Parent {
constructor(name) {
this.name = name
}
getname() {
return this.name
}
}
class Child extends Parent {
constructor(name) {
super(name)
}
}
const c = new Child('随风行酱')
console.log('aa', c.getname())
编译后:
"use strict";
function _inheritsLoose(subClass, superClass) {
subClass.prototype = Object.create(superClass.prototype);
subClass.prototype.constructor = subClass;
_setPrototypeOf(subClass, superClass);
}
function _setPrototypeOf(o, p) {
_setPrototypeOf = Object.setPrototypeOf || function _setPrototypeOf(o, p) { o.__proto__ = p; return o; };
return _setPrototypeOf(o, p);
}
var Parent = /*#__PURE__*/function () {
function Parent(name) {
this.name = name;
}
var _proto = Parent.prototype;
_proto.getname = function getname() {
return this.name;
};
return Parent;
}();
var Child = /*#__PURE__*/function (_Parent) {
_inheritsLoose(Child, _Parent);
function Child(name) {
return _Parent.call(this, name) || this;
}
return Child;
}(Parent);
var c = new Child('随风行酱');
console.log('aa', c.getname());