今天记录一下学习javascript的继承。javascript
继承基本上是基于“类”来讲的,而javascript中并不存在真正的类,因此就出现了各类模拟“类”的行为,而后就冠冕堂皇的使用起了类的概念。这里不谈“类”的概念对js学习形成了多大的困难,只说一下普通意义上继承的6种方式。java
一、原型链继承app
这是js开发人员一开始接触js,最容易学会的,因此说他就是最简单的继承的方法:函数
function Super(val) { this.val = val; this.arr = [1]; } Super.prototype.fn = function () { console.log(this.arr) } function Subtype(val, name) {} Subtype.prototype = new Super(); var b1 = new Subtype(12); console.log(b1.val) // undefined var b2 = new Subtype(); b1.fn(); // [1] b1.arr.push(2); console.log(b2.arr); // [1,2]
很容易的构造了一个子类Subtype,继承了父类的全部属性和共享方法。学习
可是缺点也很明显:this
一、子类没法传入初始参数,对于这里而言,就是Supertype没法为内部属性val传递一个合理的值;b1实例化的时候,为val传入参数12,可是b1.val却取不到该值。prototype
二、若是属性值不是一个标量基本类型,是一个对象,则会出现若是子类实例对该对象进行修改,因为对象属性所存储的仅仅是一个引用,而不是其真实值,因此会致使修改一个实例的对象属性的值,致使另外一个实例的该对象引用的值跟着变化。这里就是对实例b1的属性arr添加值操做的话,实例2引用的arr会同事变化,由于本质上他们所指向的是一个值。对象
为了修改上述问题,则引伸出了第二种继承方式:blog
二、借用构造函数继承
function Super(val) { this.val = val; this.arr = [1]; this.fn = function () { console.log('prototype'); } } function Subtype() { Super.apply(this, arguments); } var sub1 = new Subtype('s', 'zhongg'); var sub2 = new Subtype('sdsdds', 'fsdajfsldaf'); sub1.arr.push(5); console.log(sub2.arr);
很明显,修改了上述原型链继承方式的两个存在问题,可是却又产生了一个新的问题,函数不能公用的问题,因为子类是直接调用的父类函数,这就与原型prototype没有任何关系了,从而会致使定义在原型prototype上的任何函数都不起做用,这么一来,全部须要用到的函数就必须定义在父类函数内部。当子类比较多,实例化不少的时候,这就会形成每个实例化都实例化了相同的函数,会占用大量内存。
那么必然的,既然目前的解决方案还不完美,确定会有更完美的解决方案:
三、组合继承
function Super(val) { this.val = val; this.arr = [1]; } Super.prototype.fn = function () { console.log(this.val + 'prototype'); } function Subtype(val, name) { Super.apply(this, arguments); } Subtype.prototype = new Super(); var sub1 = new Subtype('值', '键值对'); var sub2 = new Subtype('值2', '键值对2'); sub1.fn();
组合继承,顾名思义,就是组合了其余的继承方式而成,这里其实就是组合了原型链继承和借用构造函数继承。
已经基本上完美了,综合了前两种继承方式的全部优势。既然说是基本上完美,那确定仍是有点瑕疵的,这里的瑕疵就是调用了两次父类函数,一次直接调用,一次new调用,致使生成了两份的实例属性,对于内存而言也是一种浪费。
到ES5时候,出现了一种能够真正完美解决继承全部缺点的继承方式:
四、寄生组合继承
function Super(val) { this.val = val; this.arr = [1]; } Super.prototype.fn = function () { console.log(10 + this.val); } function Subtype(val, name) { Super.apply(this, arguments); this.name = name; } Subtype.prototype = Object.create(Super.prototype); Subtype.prototype.fun = function () { console.log("fnu:" + this.name); } Subtype.prototype.constructor = Subtype; var sub1 = new Subtype('zhi', '名字是什么比较好呢?'); var sub2 = new Subtype('again', 'nameto'); console.log(sub1.constructor) sub1.fun(); sub1.fn(); sub1.arr.push(3);
利用的是ES5当中的Object.create(...),《你不知道的javascript》中对Object.create(..)的解释:Object.create(..)会建立一个新对象并把他关联到咱们指定的对象。这里就是建立一个新的对象 Subtype.prototype 并把他关联到 Super.prototype,这里也会产生另一个小问题,须要进行修补。就是Object.create(..)建立的对象没有constructor属性,若是须要constructor属性的话,那么在Subtype.prototype建立以后,须要手动修补,Subtype.prototype.constructor = Subtype。这么一来,继承上产生的各类问题都真正完美了。
因为Object.create(...)是在ES5中定义的,因此这个方案提出以前,其实利用的是Object.create(...)的polyfill的方案,相似于:
if (!Object.create) { Object.create = function (o) { function F (){}; F.prototype = o; return new F(); } }
可是因为该方案出现的时间比较晚,虽然此方案是真正完美的方案,可是并无上述的组合继承的方案使用普遍。事实上,组合继承的方案也基本上可以保证咱们的平常开发了。
另外还有两种继承方案,但是已经不太像继承了:
五、原型式继承
function Super(val) { this.val = val; this.arr = [1]; } Super.prototype.fn = function () { console.log(10 + this.val); } var sup = new Super('父类'); var sub = Object.create(sup); sub.func1= function(){};
// 后面就是为子类sub添加各类须要的方法或者属性
仔细比较一下,其实原型式继承和原型链继承仍是比较类似的,区别在于原型链继承的 Subtype.prototype = new Super();而原型式继承为 var sup = new Super('父类'); var sub = Object.create(sup);应该可以看出区别所在了。
六、寄生继承
最后一种继承方式真心的不是很想说,没什么可说的,其实就是把原型式继承的子类继承部分封装成函数来实现而已。
function Super(val) { this.val = val; this.arr = [1]; } Super.prototype.fn = function () { console.log(10 + this.val); } function Subtype(o){ var clone = Object.create(o); clone.func1= function(){}; // 添加各类属性 } var sub = Subtype(new Super());
在平常开发中,通常使用第三种组合继承的方式,若是想要求更高一点的话,可使用第四种寄生组合继承的方式。