让JS证明自己并不“混乱”,从《友好"挑战"掘金全体前端》引申的思考

1,473 阅读9分钟

起因

此文仅是针对于“友好"挑战"掘金全体前端(后端也看过来呗)” 这篇引战文的一个引申思考。

本人认为,讨论编程语言,需要针对应用场景和各自的强弱项。很多算法网站的实现(比如codewars、LeetCode)都提供多种语言版本的校验,本身编程语言设计思路就不同,所以去比较语法、执行效率这些,意义似乎并不是很大。我大PHP是...咳咳,你们什么都没看见,此处个人看法,如果得罪,请多多见谅。

本人曾经写过服务端代码,目前在做前端开发,写过一些java、nodejs,原生app没有写过,但风口的flutter也有粗浅涉猎。

所以,个人认为不论后端或者前端,或者所谓的全栈,大家都是开发者,只是针对的业务不同,技术的层面不同而已。对大多数人而言,我觉得,在知识碰撞中的共鸣一定是多于彼此的分歧的。

以上文章的作者,虽然表面说自己没有引战,也没有歧视。但是从他发表的沸点及文章,处处的流露出对javascript这门语言的不屑。(不好意思,我就是这么觉得的,你就算是再洗地,也不会洗白我对你的观点)

对于某些别有用心的人士,我并不好评价他们是出于什么目的,吸引关注或是哗众取宠,其实都无所谓。但他可能还意识不到,故意分化我们程序员,让大家互相嘲讽甚至言语伤害,这样为人行径,实在太过恶劣。

所以,也有了我这第一篇带着一丝愤怒的文章。

好了,言归正传,让我们一起看看javascript究竟是怎么得罪这位简介十分了得的“大牛”。

⚠以下内容,纯属个人理解,可能错误较多,️各位如有不适,请酌情食用⚠


题目

var a = { n: 1 };
var b = a;

a.x = a = { n: 2 };

console.log(a.x); // undefined
console.log(b.x); // {n:2} 

思考

此题来源可能是出自某个公司经典面试题。单从结果来看,就是一道js内存引用的问题。但是实际上,和一些道友和小伙伴一起讨论之后发现,似乎真相并没有辣么简单。

首先可能涉及到的知识点如下:

  • javascript的赋值
  • javascript的执行顺序
  • javascript表达式
  • javascript变量标志符
  • javascript变量值

好吧,看到这里,其实挺基础的。但是还没有涉及到什么cpu、寄存器、底层编译,吧啦吧啦……

最主要的,这些都是javascript的语言特征。脱离了javascript,那……咱们也不用聊了。

所以吧,今天也不会说什么底层原理。(其实底层那种深渊巨兽,我也搞不懂)

相信有很多小伙伴也已经解释了这道题为什么会有如下答案。

但是唯一不能征服那位“铁头大牛”的地方就在于a.x = a = { n: 2 }这一行的解释。

这位大牛,从内存一直谈到汇编、机器码,甚至谷歌的V8引擎。各位小伙伴也是努力的给他做了解释。

有人试图用伪代码来为他说明:

let c = a;
a = {n:2};
a.x = c;

然而,他似乎并不买账,并坚持认为这“不符合编译器优化原则”。

好吧,看到这里我确实被唬住了。

不过秉着对技术的兴趣,我也开始分析起这里究竟是怎么回事。

第一波分析

代码执行顺序:

javascript的执行顺序,是自上而下,由左至右的。

以一个简单的短路逻辑运算符为例:

let num;
console.log(num = 1 && 2); //2
console.log(num); //2

让我们拆解一下

  1. 当代码执行到log函数时,传入的参数是一个赋值语句,num = 1 && 2这是一个赋值语句。
  2. 在执行过程中,javascript解释器,碰到变量标识符num后的=时,会尝试获取后面的变量值或表达式。
  3. 1 && 2是一个表达式,最终执行的结果,是返回2
  4. num被赋值为2

由此,可以很直观的看出,代码执行的顺序是从左至右的。那么第二问题就来了,num在执行过程中究竟有没有被赋值为1呢?

猜想如下:

    num = 1 = 2

似乎看起来,没什么毛病了,但是我个人觉得,如果赋值语句设计成这样,还叫优化的话,那也太……

所以,干脆我秉着站在“javascript编译器作者肯定比我nb”的思想下,大胆猜测一次,这些站在顶尖的工程师们,他们早我一万年就想到了各种优化

所以暂时得出十(mo)分(leng)肯(liang)定(ke)的结论,num 铁定没有在赋值过程中被赋值为1的情况!!!

(不要打我,不要打我o(╥﹏╥)o)

第二波分析

那么,使用以上推论,我也大胆假设了一下。在给a.x赋值的这段代码。 执行顺序应该是如下:

  1. a.x = a = { n: 2 }
  2. a.x =
  3. a = {n:2} 赋值并返回 {n:2} 此时,变量标志符a断开与{n:1}的连接
  4. a.x = {n:2}

至此,a的值(引用对象地址)已经变为{n:2}。所以,这里也和上面num推论的赋值情况是一样的,并不会出现a.x会先赋值为a再赋值为{n:2}的情况。

(而且,如果真的出现a.x = a这一步,那么会出现一个有趣的情况,也正是因为这个情况,让我大胆的相信,那些站在顶尖的工程师,不会连这一步都没考虑到的。究竟是什么,我们后面再说。)

为了佐证一下我这些“狂妄”的“猜想”,我请出了一个神器——chrome的devtool

step 1

step 2

step 3

step 4

step 5

step 6

step 7

step 8

首先要提一点就是,chrome devtool 在打断点的时候,会有一个比较有趣的现象。就是当你用鼠标“选择”一段表达式的时候,它其实是会去执行的。

所以,我在step 3和step 4的时候,先是选择了a = {n:2},然后又选择了整句a.x = a = {n:2}

那么再回到step 5的时候,此时的a,就已经变成了{n:2}

而step 6的时候,变量标志符所保存的值仍旧是{n:1}(变量标志符a保存的旧值)

而当整段赋值语句结束后,我又看了一眼b,也就是step 7的结果。这个时候,b的值就变成了{n:1, x:{n:2}}

最后再看一眼a,emmm....已经变成{n:2}了,自然a.x也不存在了。

到这一步,我觉得才似乎有点拨开云雾的样子了。

难道你认为到这就完了吗?

no no no 作妖就要作全套的。

接着往下看,这些过程其实大家都明白。用chrome的devtool只不过是斧正一番。

所以,不甘心的我又稍微改动了一下代码:

var a = { n: 1 };
var b = a;

a = a.x = { n: 2 };

console.log(a.x); // undefined
console.log(b.x); // {n:2}

好了,现在我们再思路跳回到赋值上,执行顺序应该如下:

  1. a =
  2. a.x = {n:2} 此时a的结果仍旧是{n:1}(这还用说吗?😂)
  3. a = {n:2} 变量标志符a断开与{n:1}的连接

好了,作到这里,我觉得其实也已经不用再用devtool来执行一次断点调试了。

而,我上面的猜想也得到了以下几点佐证:

  1. 赋值的时候,碰到=并不是一定立刻就将其后面的值(基本类型值、引用值、字面量、表达式)取回来塞给前面的变量标志符。

  2. =后面是一个复杂语句(表达式)的时候,是需要先进行表达式的计算(就像num = 1 && 2)。

  3. 而当表达式也是复杂语句的时候,则遵循之前的原则,继续判断后者是否是一个复杂语句(表达式),直到得到最终一个不可分词(计算)的值。至此,再向前(从右至左)赋值,到语句结束。

到此,整个延伸思考,到此结束。


不是彩蛋的彩蛋:

上面文中提到,让我觉得写javascript解释器的人都站在顶尖。他们对优化的理解,不是一朝一夕的领悟。那么,怎么体现呢?

来看,假如a.x = a这句成立:

呵,我觉得,玩展开对象,我可以玩到天荒地老……

这里简单的说,就是出现了一个循环引用。相比之下,这样复杂的操作,我觉得是比较耗费性能的。如果在赋值语句中出现这样的情况。如果,真的在a.x = a = {n:2}中出现了这一步,我觉得,说优化的,怕不是个……


写在最后,一些无关痛痒的话:

虽然关注掘金很久,但是苦于没有什么好给大家学习的东西,所以一直都处于默默索取的状态。而,这是我的第一篇文,带着些许怒意,原因很多。

因为对方的诡辩,内心的不屑和轻视,让我也有些头脑不冷静的嘲讽了他几句。但反思一下,自己是否有扎实的基础,过硬的逻辑来辩驳呢?所以,这一篇文,也是给我自己的反思,心中也是希望自己可以能够走的更长远吧。

虽然我很生气,也没法搬出什么二进制、汇编、寄存器、cpu(我也只知道这么多名词了🤷‍♀️)来证明Javascript其实并不混乱,它也没想象中那么不堪。但是,我仍然会尽我微薄之力,维护这一份我作为程序员,作为开发者,作为一个喜爱Javascript这门语言的人的尊严。

在这里,也希望喜欢Javascript,喜欢前端各位朋友,不要自轻自贱。前端其实很包容,因为有很多人是从后端、服务端、app端、甚至其它位置转来的,也感谢掘金社区,给了我们一个能够有对等讨论机会的空间。

最后,再给那个所谓的“大牛”一份忠告:

你说的没错,我们确实还有很多知识需要去学习。不过,学习的成果,并不是用来炫耀自己的资本,也不是你嘲讽他人的理由。任何人都有自己不擅长的领域,但是,我们可以不断的探索和学习!