️ 你不知道的JavaScript: 一本非常有价值的一本前端学习书
这本让无数前端开发者“头皮发麻”又“醍醐灌顶”的书——《你不知道的JavaScript(上卷)》。正如他名字所说,此书专注于JavaScript中那些容易被误解和最难理解的核心部分,进行深入的解释和介绍。JavaScript非常特殊,需要去总结,花时间去认真学习。
1.1 编译原理
提到“编译”,你可能觉得那是C++、Java那些“老古董”语言的事儿,跟咱们灵活的JavaScript八竿子打不着。但真相是:JavaScript其实是个又旧又新的东西。
虽然大家叫它“解释型语言”,但在执行代码前,它背后有一套极其严格的“编译流程”。这就像魔术师背后的机关,你看不见,但它却决定了表演的成败。它不是那种“写完一行跑一行”的懒散模式,而是一个严谨的“三步走”:
- 分词(词法分析):这就像拆盲盒。引擎先把你的代码字符串(比如
var a = 2;)拆成一个个最小的积木块(var、a、=、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塞进去。 如果还是没懂的话,书上的例子更加透彻:
为什么分这么细?因为它们失误的后果不一样
- RHS查询失败:如果你去取货(RHS),结果快递站没这包裹(变量未声明),那不好意思,直接抛出
ReferenceError,程序崩溃。 - LHS查询失败:如果你是送货(LHS),结果没找到箱子(变量未声明),在非严格模式下,引擎会默默帮你当场造一个全局变量(隐式全局变量)当箱子,把货塞进去。
这就好比:找不到东西用会报错,但找不到地方放东西,JS引擎会大手一挥:“行吧,放大厅(全局)正中央吧!”这就导致了很多莫名其妙的全局污染。所以,"use strict"(严格模式) 就像个严格的保安,连LHS找不到箱子也直接拦下报错,不让你造隐式全局变量。
1.3 作用域嵌套
- 作用域是可以嵌套的。就像你家(全局作用域)里有个书房(函数作用域),书房里还有个保险箱(块作用域)。 规则:引擎找人时,从内向外,逐层上报,直到找到为止。
- 另一种解释:1. 儿子能用爹的遗产,爹不能用儿子的私房钱:内部作用域(子级)可以访问外部作用域(父级)的变量,但外部作用域永远访问不了内部作用域的变量。
在书中给了我们一张图来表示这种 逐层向上 的查找机制:
1.4 异常、
- 这是面试常客: ReferenceError:找名字失败(作用域判别失败,这人压根不在这个宇宙里)。
- TypeError:找属性/方法失败(作用域判别成功了,找到了对象,但对象没有你要的那个属性)。
| 异常类型 | 触发条件 | 通俗比喻 |
|---|---|---|
| ReferenceError | 作用域判别失败。 找不到变量的标识符(名字)。 | 找不到人。 你要找“张三”,但根本没这个人。 |
| TypeError | 作用域判别成功,但操作非法。 找到了变量,但对值的操作不合理(比如对 undefined 进行函数调用)。 | 找对了人,但派错了活。 你找到了“张三”,但他是个哑巴,你却让他去唱歌(报错)。 |
1.5 小结
JavaScript 的作用域机制并非简单的“查找变量”,而是一套在代码执行前就由编译器确立规则、并在运行时通过引擎与作用域进行“对话”来实施的严格体系;这套体系基于 LHS 和 RHS 两种查询方式,在嵌套的作用域链中由内向外查找变量,且对查询失败的处理极为严谨——RHS 失败抛出 ReferenceError,LHS 在非严格模式下隐式创建全局变量而在严格模式下同样抛出 ReferenceError,这种底层逻辑正是理解 JavaScript 变量行为与错误处理的关键。