本文同时也发表在我另外一篇独立博客 《Javascript: 从prototype漫谈到继承(2)》(管理员请注意!这两个都是我本身的原创博客!不要踢出首页!不是转载!已经误会三次了!)javascript
上一篇漫谈继承的结尾咱们得出了第一个比较完美的解决方案:java
function extend(Child, Parent) { var F = function(){}; F.prototype = Parent.prototype; Child.prototype = new F(); // 一旦重置了函数的prototype,须要从新赋值prototype.constructor, // 忽略这方面的介绍 Child.prototype.constructor = Child; // 保留对父类的引用, // 忽略对这方面的介绍 Child.uber = Parent.prototype; }
这个方法的“比较完美”之处就在于引入了一个中间变量var F = function(){}
。由于若是直接让子类的prototype直接继承自父类的话Child.prototype = Parent.prototype;
,出因而浅拷贝,可能对子类prototype某个属性的修改会影响到父类。而是用了一个中间变量则能避免这样的状况发生,具体请参看上一篇《Javascript: 从prototype漫谈到继承(1)》jquery
再让咱们换一种思路,继承的本质是把本身能子类能访问父类的属性和方法,那么也能够one by one的把父类的属性所有拷贝过来,像git
function extend2(Child, Parent) { var p = Parent.prototype; var c = Child.prototype; for (var i in p) { c[i] = p[i]; } c.uber = p; }
这个方法的不是那么的优雅,由于明明是能够引用父类的属性结果又都拷贝了一份。可是尽量的减小了引用,减小了查找的次数。github
从开始谈继承到如今咱们的聊的都是类与类,或者说构造函数与构造函数之间的继承,但这里是javascript,咱们还须要考虑已经实例化了的对象之间的继承,把上面的extend2稍稍改造一下就是了数组
function extendCopy(p) { var c = {}; for (var i in p) { c[i] = p[i]; } c.uber = p; return c; }
在使用这个方法的时候,传入的参数就不在是一个构造函数,而是一个实实在在的对象。这是jquery的$.extend
继承方法最基本的思想ide
上面的方法,或者说以上的全部方法,都没有解决一个深浅拷贝的问题。以上的全部方法使用的都是浅拷贝(shallow copy),你拷贝的仅仅是指向原对象内存地址的指针而已,若是你修改子类继承自父类的某个属性,极可能父类的某个属性也被修改了。好比:函数
var c = {}; var p = { pro: [1, 2, 3] } c.pro = p.pro c.pro.push(4) console.log(p.pro) //[1,2,3,4]
在上面的例子中c的pro属性继承自p.pro,pro是一个数组类型,可是当你修改c.pro时,p的pro也被修改了,这就是浅拷贝的危害!除去5种基本数据类型(Number, String, Boolean, Null, Undefined)之外的数据类型的拷贝都会是浅拷贝,可是这种危险仍是要依据对子类属性的操做方式而定。好比当子类继承自父类的某个属性时object对象:this
var c = {}; var p = { name: "lee" } c = p; c = "kill" console.log(p)
若是你把整个属性从新赋值,父类属性是不会被修改的。其中的原理是,继承的时候你是子类和父类该属性的指针都指向同一内存区域,这样的修改只是修改了子类该属性的指针,指向另外一块地方去了,可是你按下面的方式修改就有问题了spa
var c = {}; var p = { name: "lee" } c = p; c.name = "wang" console.log(p) // wang
没错,这种状况下父类也被修改了,由于此时子类和父类的该属性还在用同一块内存区域。
解决这个问题的方法也很简单,当咱们沿用上面extendCopy
方法,可是在拷贝每个属性时,咱们都去检查它是否须要深拷贝,若是须要,则进行深拷贝,这就是jquery的方法,只不过它作了更多更严密的判断,好比判断object类型和array类型的时候:
function deepCopy(p, c) { var c = c || {}; for (var i in p) { if (typeof p[i] === 'object') { c[i] = (p[i].constructor === Array) ? [] : {}; deepCopy(p[i], c[i]); } else { c[i] = p[i]; } } return c; }
在实战中咱们除了继承外还想再生产这个对象的时候添加一些本身的属性,那么咱们就能够同时用到prototype继承和一对一的拷贝属性
function objectPlus(o, stuff) { var n; function F() {} F.prototype = o; n = new F(); // 继承父类属性完毕 n.uber = o; // 继承自定义属性 for (var i in stuff) { n[i] = stuff[i]; } return n; }
最后来看看大神道格拉斯的(Douglas Crockford)提出的解决方案。
首先他提出了一个Object()
方法:
function object(o) { function F() {} F.prototype = o; return new F(); }
其实这个方法与咱们的第一个,本文开头的extend方法相似,只不过咱们的方法指定了要继承的子类
假设咱们有一个var twoD = {name: "2d shape"}
须要被继承,利用object他的解决方法是
function triangle(s, h) { var that = object(twoD); that.name ='Triangle'; that.getArea = function(){return this.side * this.height / 2;}; that.side = s; that.height = h; return that; }
留意到triangle只是一个普通的函数,而不是一个构造函数,传入的参数是你自定义化的值
基本上介绍完了,其实还一个借用构造函数来实现继承的方法,但我的以为,在上面那么多方法的陪衬下,这个方法显得不清晰,比较晦涩一些,上面方法以及足够用了。就不予介绍了。
最后原本想贴一段jquery的extend源码让大家好好感觉一下的,可是代码有点长有点宽,有兴趣的同窗能够去github的jquery源码里瞧瞧,在/src/core.js
文件中
最后吐槽一句,其实继承的本质咱们是但愿能实现如下功能
能作到以上两点,夫复何求呢!介绍了这么多方法,只是那你有一个了解,都不是完美的,你彻底能够都参考一遍而后组合一个适合本身业务逻辑的。行动吧骚年!