读完JavaScript语言精粹后 深入阅读《你不知道的JavaScript》第一章的总结

0 阅读6分钟

️‍ 你不知道的JavaScript: 一本非常有价值的一本前端学习书

这本让无数前端开发者“头皮发麻”又“醍醐灌顶”的书——《你不知道的JavaScript(上卷)》。正如他名字所说,此书专注于JavaScript中那些容易被误解和最难理解的核心部分,进行深入的解释和介绍。JavaScript非常特殊,需要去总结,花时间去认真学习。


1.1 编译原理

提到“编译”,你可能觉得那是C++、Java那些“老古董”语言的事儿,跟咱们灵活的JavaScript八竿子打不着。但真相是:JavaScript其实是个又旧又新的东西。

虽然大家叫它“解释型语言”,但在执行代码前,它背后有一套极其严格的“编译流程”。这就像魔术师背后的机关,你看不见,但它却决定了表演的成败。它不是那种“写完一行跑一行”的懒散模式,而是一个严谨的“三步走”:

  • 分词(词法分析):这就像拆盲盒。引擎先把你的代码字符串(比如 var a = 2;)拆成一个个最小的积木块(vara=2)。多余的空格?那是垃圾,直接扔掉!
  • 解析(语法分析):拆完盲盒,还得拼起来。引擎把这些积木块拼成一个“抽象语法树”(AST)。这就像是给代码拍了个X光片,把它的骨架结构清晰地展示出来,勾勒出代码的骨架。
  • 代码生成:最后,根据这个骨架,生成真正能执行的机器指令。这一步就像施工队按照图纸盖房子,最终生成可运行的代码。

所以,JavaScript并不是不懂规矩,它只是把规矩都藏在了执行之前。在这个过程中,引擎(负责执行)、编译器(负责拆解和拼装)和作用域(负责管人)这三个家伙一直在合作(沟通)。

1.2 理解作用域

1.2.1 演员表

这一节的核心是把 JavaScript 的运行机制拟人化,介绍了三位协同工作的主角。首先是引擎,它是整个程序的总导演,负责从头到尾掌控代码的编译与执行。其次是引擎的左膀右臂——编译器,它负责拆解和翻译代码(词法分析、语法树构建等),是那个在幕后干脏活累活的执行者。最后是作用域,它像一个严格的管家,负责收集和维护所有变量(标识符)的集合,并制定规则来决定当前代码对这些变量的访问权限。这三者各司其职,共同构成了 JavaScript 运行的基础协作模型。

1.2.2 对话

这一节深入展示了这三位主角是如何通过“对话”来处理一行代码的,以 var a = 2; 为例。首先在编译阶段,编译器会询问作用域:“当前作用域里有没有叫 a 的变量?”如果没有,编译器就会要求作用域声明一个新的变量 a。接着在执行阶段,引擎会再次询问作用域:“嘿,我需要对 a 进行 LHS 赋值,你能在当前作用域里找到它吗?”作用域确认后,引擎便将值 2 赋给变量 a。这个过程揭示了变量声明和赋值实际上是两个分离的动作,分别发生在编译和执行两个阶段。

到了执行阶段,引擎要干活了,它最常干的事就是找变量。这时候,它分两种情况:一种是我要把值塞给变量(LHS),一种是我要从变量里拿值用(RHS)。

LHS 是为了找到变量的容器以便赋值(送货),RHS 是为了获取变量的值以便使用(取货)。

为了更好的描述,我打个比喻:

  • RHS(取货):相当于你去快递站取快递。你只关心东西在哪,拿走就完事。比如 console.log(a),引擎就是去把 a 的值取出来打印。
  • LHS(送货):相当于你寄快递。你得先找个箱子(变量),把东西塞进去。比如 a = 2,引擎得先找到 a 这个容器,把 2 塞进去。 如果还是没懂的话,书上的例子更加透彻:

36312ecb4ad5c2af0c0366be3e8e7ace.png

为什么分这么细?因为它们失误的后果不一样

  • RHS查询失败:如果你去取货(RHS),结果快递站没这包裹(变量未声明),那不好意思,直接抛出 ReferenceError,程序崩溃。
  • LHS查询失败:如果你是送货(LHS),结果没找到箱子(变量未声明),在非严格模式下,引擎会默默帮你当场造一个全局变量(隐式全局变量)当箱子,把货塞进去。

这就好比:找不到东西用会报错,但找不到地方放东西,JS引擎会大手一挥:“行吧,放大厅(全局)正中央吧!”这就导致了很多莫名其妙的全局污染。所以,"use strict"(严格模式) 就像个严格的保安,连LHS找不到箱子也直接拦下报错,不让你造隐式全局变量。

1.3 作用域嵌套

  • 作用域是可以嵌套的。就像你家(全局作用域)里有个书房(函数作用域),书房里还有个保险箱(块作用域)。 规则:引擎找人时,从内向外,逐层上报,直到找到为止。
  • 另一种解释:1. 儿子能用爹的遗产,爹不能用儿子的私房钱:内部作用域(子级)可以访问外部作用域(父级)的变量,但外部作用域永远访问不了内部作用域的变量。

在书中给了我们一张图来表示这种 逐层向上 的查找机制:

54b6dc645a9b0daf4e8c7765f127bfab.png

1.4 异常、

  • 这是面试常客: ReferenceError:找名字失败(作用域判别失败,这人压根不在这个宇宙里)。
  • TypeError:找属性/方法失败(作用域判别成功了,找到了对象,但对象没有你要的那个属性)。
异常类型触发条件通俗比喻
ReferenceError作用域判别失败。 找不到变量的标识符(名字)。找不到人。 你要找“张三”,但根本没这个人。
TypeError作用域判别成功,但操作非法。 找到了变量,但对值的操作不合理(比如对 undefined 进行函数调用)。找对了人,但派错了活。 你找到了“张三”,但他是个哑巴,你却让他去唱歌(报错)。

1.5 小结

JavaScript 的作用域机制并非简单的“查找变量”,而是一套在代码执行前就由编译器确立规则、并在运行时通过引擎与作用域进行“对话”来实施的严格体系;这套体系基于 LHS 和 RHS 两种查询方式,在嵌套的作用域链中由内向外查找变量,且对查询失败的处理极为严谨——RHS 失败抛出 ReferenceError,LHS 在非严格模式下隐式创建全局变量而在严格模式下同样抛出 ReferenceError,这种底层逻辑正是理解 JavaScript 变量行为与错误处理的关键。