[技术分享]对 JavaScript 原型链的深入研究

shangjin发布于3 个月前 • 113 次阅读

原型链的概念


原型链的概念百度上都有,具体概念大家网上搜索,小编就来说说自己的理解,原型链在小编看来就是用来继承属性和方法的,或者说是用来保存一些公共属性和方法的,举个简单的例子,一个名叫A的男的有个叫B的后代,B有个女儿叫C,C有个儿子叫D,依次类推,最后的人叫做Z,也就是字母A-Z的组合,A为祖先,Z为末尾,在不出意外的情况下,Z从某种意义上来说,拥有前面A-Y的所有资源,也就是说Z拥有的A-Y不一定拥有,但是A-Y拥有的Z一定有,传说中的富n代啊。但是呢,有些资源是无形的,A-Y没发直接给Z,所以假如Z现在需要办工厂,Z需要资金,相关行业顾问(管理层),工人,进货渠道,销售渠道,广告渠道。Z没这些资源,于是找到他爸Y,Y说儿子创业,支持一下,于是给了Z一些启动资金,但是其他资源Y也没有,于是Y找到X,X提供了行业相关顾问这个资源,继续向上找,于是M提供了一大群工人和几个进货渠道,J提供了销售渠道,A提供了广告渠道,慢慢的Z创业的资源齐了,富n代就这么牛逼,要啥资源都有,一群屌丝只能羡慕嫉妒恨啦!以上Z这个富n代为了创业向父辈们索取资源的过程,就是原型链的精华所在。用专业的术语来说,就是

z.__proto__ = Z.prototype = new Y();
y.__proto__ = Y.prototype = new X();
...
b.__proto__ = B.prototype = new A();

至于z,x,y这些如何来的,看下面大家就不陌生了

var z = new Z();
var y = new Y();
var x = new X();

完成了以上这些,我们的富n代z童鞋办工厂的资源齐了,这就是原型链,说白了就是一个实例对象需要调用某些方法或者属性时,在自身没有的情况下,向上查找,知道找到,或者到最顶层null为止

原型链改变继承引发的问题探索


还是以z童鞋举例,我们来看第一个问题,我们先来构建个Y,按照上面说的Y有资金,那么我们给Y一个方法getMoney,以及一个Money属性

function Y() {
    this.money = '50million';
}
Y.prototype.getMoney = function(){
    console.log('z拿到了'+ this.money);
}

接着,我们需要让Z继承它,于是有了下面的代码

function Z() {
}
Z.prototype = new Y();

到这里为止一切都正常,我们只需要new Z就可以让我们的小z童鞋出现了,但是缺有个隐患,按照官方的说法,实例对象的constructor应该指向构造函数,大家来看下面的代码

var z = new Z();
z.constructor ===Z //false
z.constructor ===Y//true

下面重点来了,为何z的实例不指向本身原型呢,原因是Z.prototype = new Y();当Z.prototype等于一个新对象时,其实是覆盖了原有的对象,Z.prototype原有的对象会自带一个constructor属性,一旦被覆盖,constructor 也就没了,所以z.constructor ===Z的值才为false,constructor会沿着原型链不断向上寻找,知道找到合适的,也就是Y。

那么如何解决这个问题呢,我们上面说到Z.prototype = new Y()覆盖了原来的对象,那么我们再把constructor属性加入进去就好了,也就是Z.prototype.constructor = Z ,这个时候我们就满足了原型链定义,看着也不是这么变扭,还继续继承了Y的所有方法!

下面来张图,结合图具体给大家讲解,为了方便代码书写,我们只把继承关系写到X。

image

图中大家可以看到,Y继承了X,Z继承了Y,因为constructor被覆盖了,所以z.constructor最终指向了X这个构造函数,我们加入Z.prototype.constructor = Z和Y.prototype.constructor = Y,再来看看,重点看我新增部分即可

image

我们新增了如下代码

Y.prototype.constructor = Y // 新增
var y = new Y(); //新增,对比
console.log(y.constructor === Y) // 新增
Z.prototype.constructor = Z // 新增
console.log(y.constructor === Y) // 新增, 对比
console.log(y.constructor === Z) // 新增, 对比

我们可以从浏览器执行结果看z,constructor 已经指向了Z本身的构造函数,可喜可贺,但是大家再来看看我这里的2个y.constructor === Y,大家可以看到第一个是true,第二个是false,也就是说y.y.constructor的指向在后面发生了修改,我们查看代码,发现在Z.prototype.constructor = Z 修改过一次constructor 指向,那么会不会是这里的原因呢,于是我们用y.constructor === Z这个验证,发现果然是这样,于是我们又开始了思考,Z.prototype.constructor = Z是修改了Y.prototype.constructor的指向,让它指向了Z?或者只是单纯的覆盖,看最后一张图,我们来验证下:

image

我们通过新new一个Y的实例来验证,结果是新的实例y的constructor 指向构造函数Y,那么由此我们可以得出结论:Z.prototype.constructor = Z只是覆盖了第一个实例y的constructor,并未影响构造函数Y的原型链上的constructor

可能有的小伙伴问为啥会覆盖,不是按照原型链查找的嘛,第一个实例y应该查找到Y.prototype才对啊?大家看这句Z.prototype = y;y这个实例说白了就是对象,Z.prototype和y说白了都只是引用,开始y上的constructor是它通过Y.prototype.constructor = Y在原型链上获得的,也就是说y这个实例本身不存在constructor,只能在Y的原型链上找,但是我们用Z.prototype.constructor = Z这个一操作,相当于把y引用的对象增加了constructor属性,这个属性指向Z,所以才有后面的y.constructor === Z为true,总结下,开始的y.constructor === Y为true,是实例y在原型上活动的,y本身不包括constructor,后来的y.constructor === Z是因为Z.prototype.constructor = Z给实例y添加了一个constructor属性,所以指向了Z。

共收到 0 条回复