内容提要
《你不知道的javascript》上卷第一部分介绍了eval和with关键字的作用和问题,自执行函数、var和let关键字的特点,闭包的概念和应用,以及解决setTimeout函数中的问题的方法。
延伸解读
eval 与 with 的隐藏代价
文章指出 eval 和 with 不仅存在安全问题,还会在代码执行阶段动态改变词法作用域,导致 JavaScript 引擎在编译阶段所做的静态分析和性能优化失效。例如,eval 可以欺骗作用域,使外部变量被意外修改;with 则可能将变量泄露到全局。因此,理解这些关键字的副作用有助于在开发中避免使用它们,或至少意识到其潜在风险。
自执行函数:隔离作用域的经典手段
自执行函数通过将函数表达式包裹在括号中并立即调用,避免了创建全局变量污染作用域。文章强调,区分函数声明和函数表达式的关键在于 function 是否出现在声明开头。自执行函数常用于模拟块级作用域,尤其在 ES6 之前,是解决循环中闭包问题的常用方案。
let 如何改变循环中的闭包行为
在 for 循环中使用 var 时,所有迭代共享同一个变量,导致 setTimeout 等异步操作输出相同的最终值。而 let 会为每次迭代创建独立的块级作用域,相当于为每个循环副本绑定一个新的变量。文章通过对比示例说明,使用 let 可以更简洁地解决这类闭包问题,无需额外编写自执行函数。
闭包的本质:引用关系而非固定形式
文章强调闭包并非某种特定写法,而是内部函数持有对外部作用域引用的现象。只要函数在定义它的词法作用域之外被调用,它仍然能访问原始作用域中的变量,就形成了闭包。这种引用关系使得变量无法被回收,同时也让异步回调能够正确访问外部数据。理解这一点有助于灵活运用闭包,而不仅限于“函数返回函数”的模式。
Q&A
为什么JavaScript中不推荐使用eval和with?
eval和with都会在代码执行阶段改变词法作用域,导致JavaScript引擎在编译阶段进行的静态分析和性能优化可能失效。此外,eval还存在安全问题,因此几乎不被使用。
自执行函数有什么好处?
自执行函数可以避免创建新变量污染作用域,并且不需要函数名,能够自动运行,函数名也可能污染所在作用域。
let和var在变量提升和作用域方面有什么区别?
let不会进行变量提升,而var会提升;let不会扩大作用域范围,会生成块级作用域,而var会生成全局作用域或函数作用域。
什么是闭包?
闭包是一个函数返回另一个函数,后者引用了父函数的变量,导致父函数的作用域无法被回收。闭包强调的是一种引用关系,即内部函数持有对原始定义作用域的引用。
为什么在for循环中使用setTimeout会打印出相同的值?
因为for循环是同步的,setTimeout是异步的,异步函数会在同步函数执行完后才执行。当for循环执行完后,变量i的值已经变成了最终值,而所有的setTimeout函数引用的都是同一个i,所以打印出的都是最终值。
如何解决setTimeout在循环中打印相同值的问题?
可以使用自执行函数为每次迭代创建独立的作用域,将i作为参数传入;或者使用let关键字,为每次迭代生成块级作用域,确保每次迭代都有独立的变量。