今天是系列第四篇,主要讲一下继承相关的问题。我发现上周原型链部分还有几个概念没有说清楚,为了避免影响继承知识点的学习,我决定先把上周原型链中的prototype、constructor和__proto__这几个概念再作一下补充,也当作是前期回顾吧。es6
prototype对象用于存放同一类型实例的共享属性和方法,目的是为了减小内存消耗。举个生活中的例子来理解这个概念:咱们每个家庭都有购物和治病的需求,可是不可能每一个家庭都建造一个超市和医院,这样会形成很大的资源浪费。现代化作法是在公共区域创建一个能够共用的超市和医院,知足全部当地人的须要,这样让人们获得了实惠,资源也被很好的使用了。以下图:app
constructor就是一个指向自身构造函数引用的属性。通常存在对象.constructor === 构造函数,这个概念在接下来的继承中会有涉及。而且constructor其实是被当作共享属性放在它的原型对象中。因此咱们能够看作prototype.constructor === 构造函数,这个对象就是当前构造函数的原型。以下图:函数
我以前看到过一个结论,即:构造函数.prototype.constructor === 实例对象.__proto__.constructor === 构造函数。
咱们已经知道了原型和构造函数之间的关系,如今能够看做有了实例对象以后怎么跟原型产生一种关系来实现与构造函数之间的联系。那么咱们可能会设置一个属性指向原型,那么这个属性就是__proto__。
有了这个基础咱们就能得出下面的结论:post
实例对象.__proto__ === 构造函数.prototype 实例对象.__proto__.constructor === 构造函数
若是运用原型链的知识,还有以下结论:学习
实例对象.constructor === 实例对象.__proto__.constructor
查找对象属性时会先看当前对象是否存在该属性,若是不存在则会去其原型链上找,若是原型链上没有,则返回undefined。实例对象不存在constructor属性,则会去原型链上找,因此和实例对象.__proto__.constructor找的是同一个值。
__proto__的基本解释以下图:this
有了上面的知识以后,能够梳理继承的知识了。如下是市面上常见的5种继承方式:spa
缺点:prototype
// demo1 function Parent(name){ this.name = name } function Son(){} Son.prototype = new Parent('凉凉') Son.prototype.constructor=Son const son1 = new Son() const son2 = new Son() console.log(son1.name, son2.name) // 凉凉 凉凉 // demo2 function Parent(){ this.animals = ['老虎', '狮子'] } function Son(){} Son.prototype = new Parent() Son.prototype.constructor=Son const son1 = new Son() const son2 = new Son() son1.animals.push('猴子') son2.animals.push('猪') console.log(son1.animals) // ["老虎", "狮子", "猴子", "猪"]
本质是:将父类的实例赋值给子类的原型,让子类的原型拥有父类的全部属性和原型。可是原型上的全部属性都是共享的,因此任何一个子类实例修改了原型中的属性,其余实例获取到的属性值也会引发变化。
另外还注意一点:上面的例子Son.prototype.constructor在默认状况下是指向函数Parent,因此须要从新设置一下指向Son.prototype.constructor=Soncode
看例子:对象
function Parent(name){ this.name = name this.animals = ['老虎', '狮子'] } Parent.prototype.getName = function(){ return this.name } function Son(name){ Parent.call(this, name) } const son1 = new Son('小李') const son2 = new Son('小王') son1.animals.push('猴子') son2.animals.push('猪') console.log(son1.name) // 小李 console.log(son1.animals) // ["老虎", "狮子", "猴子"] console.log(son1.getName()) // throw error
本质是执行了一遍父类的构造函数,并让父类构造函数的this指向子类构造函数的this(即this指向子类的实例),因此子类的实例拥有和父类实例同名属性,可是没有继承原型对象。
看例子:
function Parent(name){ this.name = name this.animals = ['老虎', '狮子'] } Parent.prototype.getName = function(){ return this.name } function Son(name){ Parent.call(this, name) } Son.prototype = new Parent() const son1 = new Son('小李') const son2 = new Son('小王') son1.animals.push('猴子') son2.animals.push('猪') console.log(son1.name) // 小李 console.log(son1.animals) // ["老虎", "狮子", "猴子"] console.log(son1.getName()) // 小李
从结果的输出来看挺完美。可是在继承的过程当中Parent函数执行了两次,而且子类的原型对象和原型链中会出现两个相同的同名属性。由于原型链继承和借用构造函数继承都分别执行了一次。
说到这里我有一个疑问:为何组合继承可以把上面两个继承的优势都发挥出来呢?
答:借用构造函数的方式会在子类的实例对象上建立父类的同名属性,原型链继承的方式会在子类的原型上拥有父类的属性和原型。可是在访问某个对象的属性时,会先在当前对象中找有没有该属性,若是不存在就会去它的原型上找。因此会先去找经过call/apply绑定在当前对象上的属性,而不是原型中的共享属性。因此能够获取到子类实例的属性和原型。
为了解决父类构造函数执行两次的问题,又推出了寄生组合继承方法。
看例子:
function Parent(name){ this.name = name this.animals = ['老虎', '狮子'] } Parent.prototype.getName = function(){ return this.name } function Son(name){ Parent.call(this, name) } Son.prototype = Object.create(Parent.prototype) Son.prototype.constructor = Son const son1 = new Son('小李') const son2 = new Son('小王') son1.animals.push('猴子') son2.animals.push('猪') console.log(son1.name) // 小李 console.log(son1.animals) // ["老虎", "狮子", "猴子"] console.log(son1.getName()) // 小李
实质是:经过Object.create(obj)建立一个原型是obj的空对象赋值给子类的原型。还有一点须要注意:全部基于原型链继承的都须要记住constructor的指向问题,寄生继承至关因而原型链继承的一种变形。
基于原型链的继承若是constructor没有从新设置指向的话,它指向的是超类型构造函数。由于constructor是原型的一个共享属性,因此在子类原型中查找constructor属性时其实会在原型链上去找constructor指向的值,最后指向了超类型构造函数。看例子:
function Parent(){} function Son(){} Son.prototype = new Parent() console.log(Son.prototype.constructor) // Parent
最后一个是经过class,extends关键字实现继承的方式。es6继承具体函数和关键字的做用是什么,下一篇文章会单独拎出来说,先看一个class继承的例子:
class Parent{ constructor(name){ this.name = name } getName(){ return this.name } } class Son extends Parent{ constructor(name, age){ super(name) this.age = age console.log(new.target) } introduce(){ return `我叫作${this.name},今年${this.age}岁了` } } const s = new Son("小李", 8) console.log(s.introduce()) // 我叫作小李,今年8岁了 console.log(s.getName()) // 小李
原型继承
缺点:
借用构造函数的继承
组合继承
寄生组合式继承
es6继承