少侠们好~
又是一段时间没见了,新的一年了,希望各位少侠最近过得都还好。
上次和大家说到了对象组合中的 mixin 和 forward,
本来按道理这次应该继续对象组合的话题聊下去,
但是!
由于接下来的内容会稍微更复杂一些,
同时我想着有些少侠出于某些奇怪的原因,根基可能不太稳。
再加上上次结尾说了让你们别猜,猜不到的,有些人应该不信!
所以,这次的内容中的 JS 知识可能是次要的,更重要的是和大家分享一些小思想和经验,
毕竟某个具体的 JS 知识点可能只能帮助少侠你解决某个 JS 问题,而一些通用的小套路可能会在多个领域对你有帮助。
从标题少侠你应该也知道了,这次我们的内容会和一些基础知识有关,一些你可能都不会怎么放在心上的内容,但是,如果你轻易的忽视它们,在关键时刻,它们却会对你产生蝴蝶效应般的作用,而你甚至都意识不到这一点。
所以,这次的内容可以算是一半技术文章,更重要的是思想~
tip:这篇文章是接着上一篇文章写的,最好是按顺序看过来的比较好。
上一篇链接:
回到起点,深入理解简单事物
一般来说,不管做什么,深入理解简单基础的事物都是很重要的。
只有深入理解了基础事物,才能更好的应对在此这上更复杂的情况。
所以这里我们就聊一点容易被忽视的基础知识,
在 JS 中也一样,总有一些最基础必不可少的东西。
这里我们要聊到的是值。
什么是值呢?
值可以看做是 JS 中的最基础的元素,就像我们世界中的基础元素,氢元素,氧元素等等,
而在 JS 世界中,这些元素就是字符串,数字,布尔值等等。
除此之外,还有一些稍微复杂一些的元素,
比如对象,数组,以及函数等等。
知道了值之后,另外一个和它息息相关的内容是表达式。
什么是表达式呢? 简单来说,能够直接转化成值的东西,就叫做表达式。
记住了,少侠,表达式可以直接转化变成一个值,但是它并不是一个值。
这次的内容很简单吧?
没错,都是基础知识,我们接着来看另外一个内容,赋值。
这个时候可能会稍微有趣一些了,因为赋值虽然比较简单,但是正因为它比较简单,反而会让少侠你很容易就忽略掉一些重要的东西。
你可以给一个变量赋值:
给一个对象属性赋值:
还可以给一个数组赋值:
那么问题来了,
少侠你有没有想过,当你给一个变量,或者对象属性赋值时, 你到底是在往里面放什么东西,
或者说,你应该在右边放什么东西?
当然是一个值了对吧? 你不可以说,我在变量里面放一个运算符,比如 + 号。
你也不能直接放各种语句,至少在 JS 里面不能:
你可以放的只有值:
But! 生活总会有意外情况~
有时候,右边会是一个表达式。
??? 右边变成了一个表达式,是不是意味着我们刚才说的右边只能是一个值的规则不适用了呢?
不是! 记住了,少侠,赋值赋值,所以右边必须得是一个值。
之所以表达式也可以放在右边,是因为表达式可以变成一个值,所以我们可以先解析它,获取到一个值,然后放进去:
“为什么可以在右边放一个表达式呢?”
“因为表达式可以变成一个值!”
“为什么表达式可以变成一个值呢?”
“因为可以变成值的东西就是表达式!”
“为什么。。。”
“哪有那么多为什么!”
所以,少侠你可以在右边放置任何表达式,但是,最终,它们都会转成一个值。
不管这个值是数字,字符串,还是函数,只要转换出一个可用的值,就可以了。
Ok~ 再重复一遍,如果右边是一个表达式,它会先转换成一个值,而且,重点是它会到此为止!
“真啰嗦,这么基础的东西,还说这么大一堆!”
“是啊是啊,还重复说!”
没错! 为什么要重复说这个基础的东西呢?
因为它会产生蝴蝶效应,
还记得上节最后提到的问题吗?
为什么 obj1.fn = () => { obj2.fn(); } 会在箭头函数调用时再去查找 obj2.fn,而直接赋值 obj1.fn = obj2.fn 却不在调用 obj1.fn 时再去查找 obj2 中的 fn 呢
也就是下面这样的情况:
从上一节的内容中,少侠你应该知道,第一种方式的话,属于 early-bound,而第二种方式,属于late-bound。
一个大的区别就是我们在过后改变了 obj2.fn 的函数后,一个会更新,另一个不会。
第一种方式:
第二种方式:
造成这种差异的原因就是赋值规则导致的:
第一种赋值情况:
这里右边是什么呢? 右边是 obj2.fn
第二种赋值情况:
这里右边是什么呢? 右边是 () => obj2.fn();
重点来了! obj2.fn 是一个表达式! 而 () => obj2.fn() 是一个函数!
我们已经提到过,赋值的右边必须是值,而 obj2.fn 是表达式,所以我们必须解析它,直到获得一个值。
而另外一种情况呢?
() => obj2.fn() 是一个函数,而函数本身就是一个值,所以我们不需要做任何操作!
我们这里并不会再去关心函数内部 obj2.fn 是什么情况,因为函数还没有触发,而赋值的话由于外部箭头函数已经是一个值了,所以到这也就结束了。
所以,第一种情况下,赋值结束后,obj1.fn 和 obj2.fn 没有任何关联了,
每次调用 obj1.fn,会直接调用这个函数。
而第二种情况则会和 obj2.fn 有关联:
每次调用 obj1.fn 时,进入到箭头函数内部,都会重新查找一遍 obj2.fn,并调用它。
很神奇吧?
现在少侠你知道为什么obj1.fn = obj2.fn 会立刻查找 obj2.fn 的值了吧,因为 赋值操作右边必须是值,而 obj2.fn 不是,所以会先解析它,直到获取到一个可用的值。
而 obj1.fn = () => obj2.fn(); 由于右边就是一个函数,函数本身就是一个值了,所以会到此为止,并不会再去关心函数内部什么情况。
造成这样差距的,就是和一个小小的赋值运算有关。
“有时候,让你陷入麻烦的并不是你不知道的事,而是那些你自以为了解,但实际上却是错误的事。————马克吐温”
直觉与规则,应该相信哪一个?
这里另外一个有趣的地方就是函数必须是惰性的,这样我们才可以利用一个中间的箭头函数实现 late-bound obj2.fn。
惰性的意思也就是说,函数内部的事情,必须要在它被调用时候,才产生效果。
你不调用它的时候,就什么也不会发生。
即使它内部可能很危险:
这个其实挺合理的,尽量只加载必要的内容,在需要时候再去查找相关的内容,肯定比每次一开始就将所有内容全部加载要好很多。
所以,就算你在函数内部引用了一大堆变量,也只会在调用它时才开始查找:
甚至你函数内部引用了未定义的变量,只要你不触发它,也不会报错:
理所当然的,既然是在函数调用时才开始查找对应的元素,那么如果元素中途发生了改变,也应该以最后一次为准,对吧?
举个更贴近现实的例子,
如果一个妹子和你只是普通朋友关系,直接亲可能脸都要被打肿,但是如果先想办法追到手,变成女朋友了,情况可能就不一样了。
当然,这里还是顺便说一下如果有普通朋友关系也可以亲的妹子请联系我!
“。。。???”
“一天都弄些什么乱七八糟的例子!!!”
好了,现在我们回头看的话,下面这样的情况少侠你应该就很好理解了:
OK,现在,少侠你知道了赋值的规则,知道了函数是惰性的,知道了如果函数中引用了一个变量,在函数调用时才会去查找,也知道了应该以这个变量最后调用时的值为准,balabala。。。
按照道理来说,少侠你应该已经能够处理相关的问题了,
但是~
少侠你的直觉有时候会欺骗你
这里调用两次函数的结果是什么呢?
如果少侠你认为是 0 和 1 的话,就说明在 JS 规则和你的直觉之间,你相信了后者。
正确答案是 2 2。
如果少侠你真的按照我们所说的JS规则,一步一步分析的话,结果应该是下面这样:
而认为结果是0 和 1 的原因是少侠你的大脑忽略了 JS 世界中的规则,转而采用了更快更轻松的直觉感受,你的直觉会发现每次遍历时 i 是 0 和 1,它会自然的认为函数里的 i 每次也是 0 和 1。
这是你大脑第一反应的规则:
但事实上,是函数里面的 i 和遍历时的 i 没有关系,因为函数是惰性的,它里面的 i 要等到调用时才开始查找。
跟随大脑的想法
好了,经过上面的内容,少侠你应该知道了规则和直觉有时候会产生冲突,
大脑的第一直觉不一定总是可靠。
不过,
大脑虽然会犯错,但是它也有很多好处,
比如如果不是它知道如何控制你用嘴巴吃饭,你可能会饿死!
另外一个好处就是它也喜欢观察并总结发现的事物规律,
大脑经常也会很好奇,可能会产生一些奇怪的脑洞,有时候,跟随大脑的想法,也能够帮助你发现新大陆。
比如,我们上面的函数都没有使用到 this,
如果你碰巧又才学习了 this,
你的大脑可能会想,如果将 this 考虑进来,会发生什么事呢?
这里我们结合上一节的内容看看,
第一种情况:
第二种情况:
很熟悉的内容,因为这就是我们上一次提到的 mixin 和 forward 的情况。
由于上一节已经详细说过这些内容了,所以这里就简单说下结果,
第一种情况调用 obj1.fn 会打印出 obj1 中的 name,‘天辰dreamer’。
而第二种情况调用 obj1.fn 会打印 obj2 中的 name,也就是 ‘乌云dreamer’,
而且由于我们的箭头函数是惰性的原因,第二种情况可以在中途改变 obj2.fn 的内容,obj1.fn 会自动更新。
还记得我们上一次的表格吗?
组合方式 | bound 类型 | 方法中的作用对象 | 数据是否独立 |
---|---|---|---|
mixin | early-bound | 对象本身 | 是,每个对象的操作不会影响其他 mixin 对象 |
forward | late-bound | 用于组合的对象 | 否,用于所有对象会共享 forward 进来的对象数据,所以会互相影响 |
如果少侠你仔细观察的话,你可能会想,
obj1.fn = obj2.fn 中,它是 early-bound,同时方法中的 this 作用于 obj1。
而 obj1.fn = () => { obj2.fn() } 中,它是 late-bound, 同时方法中的 this 会指向 obj2。
是不是好像缺了两种情况?
比如能不能实现 early-bound,而 this 作用于 obj2,
或者实现 late-bound, this 却作用于 obj1 上呢?
哈哈哈哈哈哈~
没想到吧!又绕回来了!
剧情居然突然变得陡峭了起来,基础不稳的少侠有没有躲在一旁瑟瑟发抖?是不是有点措手不及?
“。。。。。。。。。。”
好了,严肃脸!
不是故意花里胡哨,这些内容是我认为少侠你应该理解的,理解它们对你熟练掌握JS很有帮助,同时能避免很多日常坑,也对我们接下来要遇见的 prototype 有很大帮助。
如果你觉得没用的话,我不要你觉得,我要我觉得!
那么,最后两种情况到底能不能实现呢?
“当然可以了,不可以的话,天辰你提它们干嘛?”
“我随便说一说不行嘛?”
“骗谁呢,肯定可以。”
“那我这里说它不可以。”
“不可以算了,反正我们也没兴趣。”
“少侠你怎么能说放弃就放弃呢? 再试一试。”
“不试!”
“我纠正一下刚说过的话,是可以实现的。”
“没兴趣了。”
“不会很难的。”
“就不试!”
“。。。。。。”
天辰突然和自己内心幻想出来的角色吵了起来,一时生气直接跑走了,临走前扔下了一张皱巴巴的小纸条。。。
1、函数是惰性的,所有放在函数内部的变量,都会在该函数调用时才开始查找,所以 early-bound 和 late-bound 的关键区别可能是元素放置在函数内外的不同。
2、你可以通过将普通函数放置在不同对象上调用来改变其中的 this 指向,你需要 this 指向 a, 你就放在 a 上调用,你需要 this 指向 b, 你就放在 b 上调用。
3、重新回顾一下我们最开始提到的很基础的规则可能会有帮助。
4、少侠,江湖路远,有缘再见~
一些你可能关心的问题
1、感觉更新有点慢,天辰你能不能更新快一点?
不好弄,要有灵感了才能写,而且如果我自己读着都比较尴尬的话就不好意思发出来,得反复改很多次才行。。。
2、说好的蓝胖dreamer图片呢?
好了,这就是可爱又听话的蓝胖dreamer了~
你可能会以为蓝胖会是只英短猫,但是我叫它蓝胖的原因只是因为它眼睛是蓝色的很好看,然后它长得又比较胖。。。
3、这次的文章有点奇怪,感觉好像你说了一大堆,又感觉好像什么都没说。
很好,说明我的文章已经超出了单纯的文章本身!已经有点道的感觉了,只可意会不可言传~
3、好吧好吧,那么下一次会有 prototype 吗?!
为什么要急着遇见什么呢?少侠。
说不定当我们真正遇见了 prototype 之后,你反而会开始怀念起现在的时刻呢~
4、文章结尾是不是太仓促随意了?
这还叫仓促随意?少侠你应该是没有看过更仓促的结尾方式,比如:
声明:本文仅限于潇洒有趣又很酷的天辰dreamer装逼使用,转载请注明原作者和出处,商业转载请联系我(如果真有的话)。。。