手记

少侠, JS基础知识产生的蝴蝶效应能有多大?~

少侠们好~

又是一段时间没见了,新的一年了,希望各位少侠最近过得都还好。

上次和大家说到了对象组合中的 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装逼使用,转载请注明原作者和出处,商业转载请联系我(如果真有的话)。。。

3人推荐
随时随地看视频
慕课网APP