好久之前学习《Javascript语言精粹》时,写过一个关于js的系列学习笔记。es6
最近又跟别人讲什么原型和继承什么的,发现这些记忆有些模糊了,而后回头看本身这篇文章,以为几年前的学习笔记真是简陋。express
因此在这里将这篇继承从新更新一下,而且加上ES6的部分,以便下次又对这些记忆模糊了,能凭借这篇文章快速回忆起来。闭包
本篇文章关于ES5的继承方面参考了《Javascript语言精粹》和《JS高程》,后面的ES6部分经过使用Babel转换为ES5代码,而后进行分析。函数
既然讲到继承,那么有必要先说一下对象是如何经过构造器构造的。性能
先看如下代码:学习
function Parent(){ this.type='人' this.name='爸爸'; this.body={ weight:50 } }
当声明了Parent这个构造函数后,Parent会有一个属性prototype,这是Parent的原型对象。this
能够至关于有如下这段隐式代码es5
Parent.prototype={constructor:Parent}
执行构造函数构造对象时,会先根据原型对象创造一个对象A,而后调用构造函数,经过this将属性和方法绑定到这个对象A上,若是构造函数中不返回一个对象,那么就返回这个对象A。prototype
而后能够看下js最初的继承。code
如下为最基本的原型链玩法
function Parent(){ this.type='人' this.name='爸爸'; this.body={ weight:50 } } function Child(){ this.name='儿子'; } Child.prototype=new Parent(); var child=new Child(); console.info(child.name);//儿子 console.info(child.type);//人
经过以上发现,咱们的child继承了原型对象的type。
原型链最大的问题在于,当继承了引用类型的属性时,子类构造的对象就会共享父类原型对象的引用属性。
咱们先来看以前的例子,child不只继承了parent的type,也继承了body。
修改一下以上的代码:
var child=new Child(); child.body.weight=30; var man=new Child(); console.info(man.type);//30
这里能够看到,原型中的引用对象被child和man共享了。
子类构造函数构造child和man两个对象。
当他们读取父级属性时,读取的是同一个变量地址。
若是在子级对象中更改这些属性的值,那么就会在子级对象中从新分配一个地址写入新的值,那么就不存在共享了属性。
可是上面的例子中,是更改引用对象body里的值weight,而不是body。
这样的结果就是body的变量地址不变,致使父级引用对象被子级对象共享,失去了各个子级对象应该有的独立性(这里我只能用独立性来讲明,为了不和后面讲到的私有变量弄混)。
因而就有了借用构造函数的玩法:
function Parent(){ this.type='人' this.name='爸爸'; this.body={ weight:50 } } function Child(){ Parent.call(this); this.name='儿子'; } Child.prototype=new Parent(); var child=new Child(); console.info(child.name);//儿子 console.info(child.type);//人
实际上借用构造函数是在在子类的构造函数中借用父类的构造函数,而后就在子类中把全部父类的属性都声明了一次。
而后在子类构造对象后,获取属性时,由于子对象已经有了这个属性,那么就不会去查找原型链上的父对象的属性了,从而保证了继承时父类中引用对象的独立性。
组合嘛,实际上就是利用了原型链来处理一些须要共享的属性或者方法(一般是函数),以达到复用的目的,又借用父级的构造函数,来实现属性的独立性
在上面的代码中加入
Parent.prototype.eat = function(){ // 吃饭 }
这样Child构造的对象就能够继承到eat这个函数
这个方式是道格拉斯提出来的,也就是写《JavaScript语言精粹》的那我的。
实际上就是Object.create。
它的最初代码以下:
Object.create=function(origin){ function F(){}; F.protoType=origin; return new F(); } var child=Object.create(parent);
这个玩法的思路就是之后咱们不要用js的那种伪类写法了,摆脱掉类这个概念,而是用对象去继承对象,也就是原型继承,由于相对于那些基于类的语言,js有本身进行代码重用的方式。
可是这个样子依然会有问题,就是在原型继承中,同一个父对象的不一样子对象共享了继承自父对象的引用类型。
致使的结果就是一个子对象的值发生了改变,另一个也就变了。
也就是咱们说的引用属性独立性的问题。
道格拉斯又提出了寄生式继承:
function createObject(origin){ var clone=Object.create(origin); clone.eat=function(){ // 吃饭 } }
这种继承你能够看作毫无心义,由于你通常会写成下面这样:
var clone=Object.create(origin); clone.eat=function(){ // 吃饭 }
这个样子写也没毛病。
固然你能够认为这是一次重构,从提炼一个业务函数的角度去理解就没毛病了。好比经过人这个原型创造男人这个对象。
组合继承的问题在于,会两次调用父级构造函数,第一次是创造子类型原型的时候,另外一次是子类型构造函数内部去复用父类型构造函数。
对于一个大的构造函数而言,可能对性能产生影响。
而原型继承以及衍生出的寄生式继承的毛病就是,引用类型的独立性有问题。
那么堪称完美的寄生组合式继承就来了,可是在以前,咱们先回顾下这段组合式继承的代码:
function Parent(){ this.type='人' this.name='爸爸'; this.body={ weight:50 } } function Child(){ Parent.call(this); this.name='儿子'; } Parent.prototype.eat=function(){ //吃 } Child.prototype=new Parent(); var child=new Child();
那么如今咱们加入寄生式继承的修改:
function Parent(){ this.type='人' this.name='爸爸'; this.body={ weight:50 } } function Child(){ Parent.call(this); this.name='儿子'; } Parent.prototype.eat=function(){ //吃 } function inheritPrototype(childType,parentType){ var prototype=Object.create(Parent.prototype); prototype.constructor=childType; childType.prototype=prototype } inheritPrototype(Child,Parent) var child=new Child();
或者咱们把inheritPrototype写得更容易懂一点:
function inheritPrototype(childType,parentType){ childType.prototype=Object.create(Parent.prototype); childType.prototype.constructor=childType; }
记住这里不能直接写成
childType.prototype=Parent.prototype
表面上看起来能够,可是childType上原型加上函数,那么父级就会加上,这样不合理。
经过inheritPrototype,Child直接以Parent.prototype的原型为原型,而不是new Parent(),那么也就不会继承Parent本身的属性,而又完美继承了Parent原型上的eat方法。
经过借用构造函数又实现了引用属性的独立性。
那么如今咱们来看就比较完美了。
只不过这种方式我日常都基本不用的,由于麻烦,更喜欢一个Object.create解决问题,只要注意引用对象属性的继承这个坑点就行。
以上全部生成的对象中都是没有私有属性和私有方法的,只要是对象中的属性和方法都是能够访问到的。
这里为了作到私有属性能够经过函数化的方法。
function Parent(){ this.type='人' this.name='爸爸'; this.body={ weight:50 } } function Child(){ Parent.call(this); this.name='儿子'; } inheritPrototype(Child,Parent) var ObjectFactory=function(parent){ var name='troy';//私有变量 result=new Child(); result.GetMyName=function(){//这是要建立的对象有的特有方法 return 'myname:'+name; } return result; }; var boy=ObjectFactory(anotherObj);
这个地方实际上用的是闭包的方式来处理。
这里其实还有一种复制属性的玩法,继承是经过复制父对象属性到子对象中,可是这种玩法须要for in遍历,若是要保持引用对象独立性,还要进行递归遍历。
这里就不介绍了。
它有它的优势,简单,避开了引用对象独立性,而且避开了从原型链上寻找对象这个过程,调用属性的时候更快,缺点是这个遍历过程,对于属性多层级深的对象用这种玩法,不是很好。
ES5一直都是有伪类继承(也就是经过构造函数来实现继承)和对象继承(我认为原型和拷贝都算这种)两种玩法,一种带着基于类的思想的玩法,一种纯粹从对象的角度去考虑这个事情。
若是加上类的概念的话,确实麻烦,若是是直接考虑原型继承而不用考虑类的话就简单不少。
因此对ES5而言能够更多从对象角度去考虑继承,而不是从类的角度。
在ES6中出现了class的玩法,这种类的玩法使得我在使用ES6的时候更愿意去站在类的角度去思考继承,由于基于类去实现继承更加简单了。
新的class玩法,并无改变Javascript的经过原型链继承的本质,它更像是语法糖,只是让代码写起来更加简单明了,更加像是一个面向对象语言。
class Parent { constructor(name){ super() this.name = name } eat(){ console.log('吃饭'); } } class Child extends Parent { constructor(name,gameLevel){ super(name) this.gameLevel = gameLevel } game(){ console.log(this.gameLevel); } }
咱们能够经过Babel将它转化为ES5:
'use strict'; var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; } function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } var Parent = function () { function Parent(name) { _classCallCheck(this, Parent); this.name = name; } _createClass(Parent, [{ key: 'eat', value: function eat() { console.log('吃饭'); } }]); return Parent; }(); var Child = function (_Parent) { _inherits(Child, _Parent); function Child(name, gameLevel) { _classCallCheck(this, Child); var _this = _possibleConstructorReturn(this, (Child.__proto__ || Object.getPrototypeOf(Child)).call(this, name)); _this.gameLevel = gameLevel; return _this; } _createClass(Child, [{ key: 'game', value: function game() { console.log(this.gameLevel); } }]); return Child; }(Parent);
而后在这里咱们不考虑兼容性,提炼一下核心代码,并美化代码以便阅读:
'use strict'; var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key,descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); function _inherits(subClass, superClass) { subClass.prototype = Object.create(superClass.prototype); subClass.__proto__ = superClass; } var Parent = function () { function Parent(name) { this.name = name; } _createClass(Parent, [{ key: 'eat', value: function eat() { console.log('吃饭'); } }]); return Parent; }(); var Child = function (_Parent) { _inherits(Child, _Parent); function Child(name, gameLevel) { Child.__proto__.call(this, name); var _this = this; _this.gameLevel = gameLevel; return _this; } _createClass(Child, [{ key: 'game', value: function game() { console.log(this.gameLevel); } }]); return Child; }(Parent);
在这里能够看到转化后的代码很接近咱们上面讲述的寄生组合式继承,在_inherits中经过Object.create让子类原型继承父类原型(这里多了一步,Child.__proto__=Parent),而在Child函数中,经过Child.__proto__借用父级构造函数来构造子类对象。
而_createClass不过是把方法放在Child原型上,并把静态变量放在Child上。
总的来讲ES6中,用class就好,可是要理解这个东西不过是ES5的寄生组合式继承玩法的语法糖而已,而不是真的变成那种基于类的语言了。 若是有天还让我写ES5代码,父类中没有引用对象,那么使用Object.create是最方便的,若是有,那么能够根据实际状况考虑拷贝继承和寄生组合式继承。