词法作用域和闭包的理解
在日常工作中,我们会编写业务逻辑相关的代码,尽管实现了模块功能,但是JavaScript内部究竟是怎么去定义和引用这些代码,最基本的概念需要搞清楚,那就是什么是作用域?什么是词法作用域?与词法作用域相关的闭包是什么?怎么在日常工作中发现自己代码中的闭包?
首先,先来看作用域是什么,作用域是根据名称查找变量的一套规则;简单理解作用域就是一套规则,用于确定在何处以及如何去查找变量或标识符;查找规则一般有LHS和RHS查询。这套规则用来管理引擎如何在当前作用域以及嵌套的子作用域中根据标识符名称进行变量查找;JavaScript所采用的的作用域模型是词法作用域。
那么词法作用域就是定义在词法阶段的作用域,可以理解由你写代码时将变量和块作用域写在哪里来决定的。或者,是由书写代码时变量和函数的位置来决定的。总之词法作用域就是在写代码或者说定义时确定的。
let关键字可以将变量绑定到所在任意作用域中,可以说let为其声明的变量隐式地劫持了所在的快作用域;
闭包:闭包使得函数可以继续访问定义时的词法作用域。
我用一个综合例子来深入理解下:
function foo(a) { console.log(a + b); } var b = 2; foo(2);
// 执行foo函数时,变量a形参,作用于函数内部,b会在上一层作用域(全局作用域)中查找
// 理解词法作用域 function foo(a) { var b = a * 2; function bar(c) { console.log(a, b, c) } bar(b * 3); } foo(2); // 2,4,12 /** * 包含着整个全局作用域,其中只有一个标识符:foo * 包含着foo所创建的作用域,其中有三个标识符:a、bar和b * 包含着bar所创建的作用域,其中只有一个标识符:c * 作用域有其对应的作用域块代码写在哪里决定,它们是逐级包含的 */
// 预期是分别输出数字1~5,每一秒一次,每一次一个。实际输出五次6 for (var i = 1; i <= 5; i++) { setTimeout(function timer() { console.log(i); }, i * 1000); } // 我们再来试下这种方式 for (var i = 1; i <= 5; i++) { (function () { setTimeout(function timer() { console.log(i); }, i * 1000); })(); } /** * 答案是不行,虽然拥有的多个词法作用域,延迟函数的确也会将每次迭代中创建的作用域封存起来, * 如果作用域是空的,它没有一个实质的内容(变量)为我所用,改进后 */ for (var i = 1; i <= 5; i++) { (function () { var j = i; setTimeout(function timer() { console.log(j); }, j * 1000); })(); } /** * 将j存放在函数内部,在改进 */ for (var i = 1; i <= 5; i++) { (function (j) { setTimeout(function timer() { console.log(j); }, j * 1000); })(i); }