少侠们好,
上次和大家聊了一些关于this的故事,在最后,我们也说了,还有一些相关的内容和问题没有谈到,
比如,prototype原型链,
不管是在你阅读文章,还是面试时,可能都会被问到关于它的问题。
如果少侠你看过关于它的文章,
你很可能已经见过一些和它相关的字眼,prototype,_proto_,new,this 等等。
但是,即使你对这些字眼都很熟悉了,你可能也会有一个疑惑,
原型链到底是什么? 到底是怎么来的,又有什么作用呢?
为了弄清楚它,在这次的故事中,我们会先讲述一些关于对象其他的内容,这些内容能够帮我们最终过渡到原型链去,并真正了解它做了什么。
它们可能与原型链无关,但是以后却能够帮助你更好的理解原型链。
这就是为什么我们的标题会是
prototype前传
好了,下面开始进入正题~
冰山之下
少侠,如果你看过上一节关于 this 的故事的话,你应该已经知道,之所以会有 this 存在,其实就是因为我们需要有这样一个动态的东西。
用来干嘛呢? 用来做对象组合,这样我们就可以复用和组合现成的对象,来完成新的功能。
了解事物的本质这一点非常非常重要,
比如你知道了 this 出现的原因就是为了动态组合对象,所以它会动态的变来变去也就不奇怪了。
原型链也是一样,理解它的关键点就在于弄清楚它的本质是什么,实际上,它的本质和 new 没有什么必然的关系,和所谓的_proto_ 也没有太大的关系,
它的本质是对象组合。
我们可以有多种方式来组合对象,组合方式的不同,就构成了一个个不同的方法,
原型链就是其中之一,
除了它之外,也还有很多其他的方式,
所以今天,我们先不说原型链,说一说其他的组合方式~
首先,来看看我们上次的一个例子:
在这里,我们有一个 user 对象,里面包含了 user 的名称,宠物列表,以及两个对应的方法,一个用来获取名称,另一个用来添加宠物。
这种对象,大多数情况是没有什么问题的,你可以很方便的把相关的逻辑放在一起来使用,
不过,这种方式,有时候也会不太容易复用和组合,
因为它只是很简单的把数据和行为混合在了一起,会显得有点 too young too simple,
所以,我们来试着把数据和行为单独放在不同的对象里。
分开很简单,不过,光分开是没有用的,在合适的时候,我们得想办法把它们再组合起来才行,因为光有数据的话,就好比少侠你有一大笔钱,却找不到花钱的地方,而光有行为的话,就好比少侠你可以花钱如流水,但是,账上没有流水。。。
所以,它们得组合在一起才能完全发挥出作用。
怎么组合呢?
第一种组合的方式,在上节关于 this 的故事中,少侠你其实已经遇见过了,就是我们在使用 Object.assign 时的方式,它也叫做 mixin。
Mixin
Mixin 是最简单的合并对象的方式,通过将一个对象中的属性复制到另外一个对象中来实现合并。
在这里,我们通过自定义的 mixin 函数把 userActions 里面的方法混入到了 user 中。
当然,使用内置的 Object.assign 可能会好一些:
少侠你可以看到,通过 mixin , 我们的 user 对象获得了 userActions 中的方法。
mixin 的好处是你可以把 userActions 同时混入多个对象,来达到方法复用的效果。
mixin 的方式有几个特点,
第一个特点是 mixin 处理对象的方式属于 early-bound
什么意思呢? 意思是结果对象中的方法在经过 mixin 处理时就已经确定了,即使你会过后才调用它,但是它是在 mixin 那一刻就已经确定了。
比如这里我们将 userActions 混入到 user 中,user 中的 getName 或是 addPets只会取决于混入这一刻时 userActions 中的对应方法,混入完成后,即使我们过后改变 userActions 中 getName 方法,对 user 也不会有任何影响。
这就是 early-bound,也就是说,会调用哪个函数,在一开始的时刻就已经确定好了,所以是 early。
第二个特点是通过 mixin 处理的对象,最终所有的效果都会在我们的接收者上发生。
比如,我们把 userActions 混入到 user 之后,user 就是接收者,当我们调用 user.getName 方法时,它会去查找 user 上的 name,不会对 userActions 造成任何影响,只和它本身有关,就算你把 userActions 销毁了,设置为 null,也没关系。
“天辰,这不是废话吗?我都把方法放在它里面了,也已经混入完成了,调用时不和它有关,难道还和你有关?”
“对啊,这不很明显嘛?有什么好说的。”
没错,这里是很明显,也是 mixin 的特点,
但是,在另外的组合方式下,情况可能就完全不一样了!
比如接下来我们要说的 forward 方式。
Forward
另外一种对象组合的方式是 forward,它和我们的 mixin 区别有点大,因为,它既不是 early-bound,作用的效果也不是在接收者上。
我们接着上面的例子来看吧,想象少侠你已经通过 mixin 把 userActions 混入到 user 中了,
然后,现在我们遇见了新情况,一个宠物店,它可以让我们捐献宠物,
假设这是我们的宠物店对象:
如果你想给宠物店捐献宠物的话,
如何将我们的 user 对象和宠物店关联起来呢?
这种情况,少侠你不能使用 mixin(user, petStore)的方式,
除非。。。少侠你想把自己变成一个宠物店。。。
这时,我们就需要另外一种组合方式,
也就是 forward !
它可以让我们将另一个对象上的方法组合进来,并且当你调用这些方法时,它会在原来的对象上发挥作用。
类似这样:
那么 forward 长什么样呢?
见证奇迹的时刻!
我们来试试用 forward 把 petStore 整合进来:
好了,少侠应该已经发现 forward 的区别了,在这里,我们希望 userActions 中的方法作用于 user 本身,于是我们采用mixin,
但是我们却希望 petStore 上的方法作用于 petStore 本身,只不过可以在 user 上调用,我们则采用了 forward。
所以我们可以使用 user.addPet 给 user 内部的 pets 添加宠物,然后使用 user.givePet 给 petStore 内部的 pets 添加宠物。
很神奇是吧?
我们还可以把多个用户和同一个宠物店关联起来:
在这里,我们通过 forward 函数,同时把 petStore 关联到了两个不同的用户上,
然后,user1 和 user2 会共享同一个 petStore,也就是,你通过 user 添加一个宠物,再通过 user2 添加一个宠物,petStore 上会有2个宠物,这些数据针对所有用户都是同步的。
“哦,这样啊,那么共享的数据有什么用处呢?”
“对,我又不开宠物店,换个另外的例子!”
具体的用途的话,不同的少侠会有不同的理解,就像它本身体现的特点一样,如果有一些数据你想在多个不同的对象之间共享,也许你就可以试试这种方式。
当然,天辰我人比较好,
所以这里我就再给少侠你举个简单例子了,比如你页面自定义了一个路由栈:
很明显的是,路由对于所有的页面应该是共享的,不管哪个页面返回了一级,对于的路由栈就应该减少一层,所以我们使用了 forward 来将路由方法共享到每个页面中。
early-bound 与 late-bound
上面我们提到过, mixin 是 early-bound,意思是,一旦混入了一些方法,就算我们过后改变了源对象中的方法,也不会对已经混入后的对象有所影响。
那么 late-bound 又是什么意思呢?
继续我们的 forward 旅程,在之前,我们已经和宠物店建立了合作关系:
但是!现在由于天辰捐赠的小猫 蓝胖dreamer 过于调皮,所以,宠物店决定暂时不接受继续捐赠叫做蓝胖的小猫了。
好了,现在宠物店更新了新规则,那么我们之前 forward 的方法还有用吗? 是需要我们手动再 forward 一次,还是会自动更新呢?
我们来试一试
很神奇!
我们并不需要重新进行 forward ,当我们改变了 petStore 中的方法时,对应 user 中的方法会自动更新!这点和 mixin 不一样,如果是 mixin 的话,我们必须重新 mixin 一次才有效。
为什么会这样呢?
原因是 forward 中下面这段代码:
这里的关键点在于,我们并没有直接把 petStore.givePet 赋值给 user,而是利用了一个中间的箭头函数。
这样的话,在一开始,user.givePet 只是和这个箭头函数联系了起来,它并不会马上就继续去查看箭头函数里面的内容,毕竟箭头函数都还没有调用对吧?
也就是说,到我们真正调用这个箭头函数,并开始查找其中的 petStore.givePet 之间的时间段,我们是可以改变 petStore.givePet 方法的。
在这之间,不管中途 givePet 变换了多少次,就算它有一段时间变成 undefined,null 等等也没有关系,因为只要我们不触发箭头函数,它就不会去查找 petStore.givePet。
它过去是什么不重要,重要的是,我们找到它时,它是什么。
汝未看此花时,此花与汝同归于寂。汝来看此花时,此花颜色一时明白过来。便知此花不在汝之心外。——王阳明
好了,这就是 early-bound 和 late-bound 的区别了,一个是在最开始就决定好调用哪个函数了,另一个则是在调用时,才开始查找调用哪个函数。
总结一下~
组合方式 | bound 类型 | 方法中的作用对象 | 数据是否独立 |
---|---|---|---|
mixin | early-bound | 对象本身 | 是,每个对象的操作不会影响其他 mixin 对象 |
forward | late-bound | 用于组合的对象 | 否,用于所有对象会共享 forward 进来的对象数据,所以会互相影响 |
完全OK!
恭喜你,少侠~
你又成功发现并阅读完了一篇非常有趣的文章!
希望能够对你有所收获~
可惜的是,我们依然还没有说到 prototype ,
而且你可能也还有一些疑问,
没有关系,
以后还会有很多有趣的内容会提到的,
如果少侠你觉得以上内容对你有帮助的话,
希望可以帮忙给分享或点个赞,
让我感受一下江湖的温暖~
好了,江湖路远,少侠我们有缘再见~
一些你可能关心的问题
1、等了这么久的prototype原型链,结果天辰你告诉我还有前传?
别急,少侠,前传通常能够帮助你更好的了解剧情,这里也是一样,相信我,这样的路线也许能够帮你更好的过渡到原型链,而且,说不定我们一不小心就实现它了,还顺带解锁一堆隐藏的小伙伴呢~
2、好吧,老实说,forward 的实现有点没看懂。。。
哈哈哈哈,我就知道(突然出现的迷之优越感),少侠你可以这样对比 mixin 和 forward:
我们上面的 forward 函数只不过多了自动检测 obj2 中的方法,并批量赋值这么一个过程。
真正的关键点,就是一个多了一个中间箭头函数,另外一个没有。
注意了,少侠! 请你认真仔细的观察它们的区别,多测试一下,找找不同点~
3、为什么 obj1.fn = () => { obj2.fn(); } 会在箭头函数调用时再去查找 obj2.fn,而直接赋值 obj1.fn = obj2.fn 却不在调用 obj1.fn 时再去查找 obj2 中的 fn 呢
我就知道有人可能会有这个疑问!
这个问题实际上和 JS 中的赋值运算有关。
本来打算就这里说了,不过发现内容稍微有点长,就下次出单独的专题说!
少侠你可以先自行思考下,或者查找一下网上的资源,留给你当做练习题。
tip: 可以再参考一下上个问题中的流程。
4、接下来是不是要开始说关于prototype原型链的故事了?
不知道! 生活总是会给你带来惊喜,我也是!不能随便透露下面的内容,甚至下一次说的还是不是 JavaScript 都不一定,所以,别乱猜了,少侠,直接点赞关注加转发~
5、蓝胖dreamer 是个什么鬼? 真的是只调皮的宠物吗?
当然是真的宠物,不过,现实中的它一点也不调皮!反而很听话~
下一篇文章争取给它爆个照!
6、干嘛要在名称后面加个 dreamer ?
之前以为 dreamer 的意思是幻想家,像是形容那些怀揣梦想的人,感觉很酷,但是后来知道了它的意思其实也有不切实际的人,看起来很奇怪, 既有褒义又有贬义,后来我想了下,也许 dreamer 在一开始本身就是不好不坏的,与其在一开始去纠结它的意思,倒不如选择成为一个 dreamer,亲自去定义它。
Be a dreamer, not a loser. 少侠~
声明:本文仅限于潇洒有趣又很酷的天辰dreamer装逼使用,转载请注明原作者和出处,商业转载请联系我(如果真有的话)。。。