javascript 词法分析,作用域与闭包理解
首先我们看一个例子,以下 2 个函数创建过程一样吗?
function a(arg){ console.log(arg); } var b = function(arg){ console.log(arg); }
在这里,直接 function a(){} 声明变量拥有的优先级较高,因为 js 在脚本运行前会进行一个词法分析,通过下面的例子可以看出词法分析的过程
function t(greet) { console.log(greet); function greet (){ alert('hello'); } } t(3); //通过函数声明将会打印 function(){alert('hello');}
function t(greet) { console.log(greet); var greet = function(){ alert('hello'); } } t(3); //通过变量声明函数表达式 则会打印 3
JS 词法分析过程
JS 函数在运行之前,会进行一个词法分析,会为该函数生成一个 Active Object,以下简称 AO,该函数作用域下能找到的所有变量,都在 AO 上,词法分析过程如下:
1.生成一个 Active Object 为空对象 t.AO = {}
2.分析函数接收的参数,如没有,则略过
3.分析函数内变量声明,如没有,则略过, 如果 AO 对象上有同名属性,则不作任何操作
4.分析函数内的函数声明,如没有,则略过, 如果 AO 对象上有同名属性,则将覆盖该属性值
5.执行该函数
第三步与第四步的区别就在于函数声明会将函数接收的参数与变量声明覆盖,这就是为什么函数声明要比函数表达式优先级更高的原因.
接下来我们看一个更复杂的 Demo
function a(b) { alert(b); var b = function (){ alert (b); } b(); } a(1);
这个例子会 alert 出什么呢?
结果是:先 alert 出 1 然后是 function(){alert(b);}
按照上面词法分析过程看整个函数调用
执行 a 函数: 对 a 函数进行词法分析
1.调用 a 函数,分析 a 函数: a.AO = {}
2.分析参数 a.AO = {b:1}
3.分析变量声明 有,但与参数 b 同名,略过
4.分析函数声明 没有,略过
第 2 行:alert(1)执行
第 3 行:a.AO.b 被赋值 为一个函数表达式
第 6 行:执行 b 函数,则 a.AO.b (此时是一个函数) 被执行
执行 b 函数: 对 b 函数进行词法分析
1.b.AO = {}
2.分析参数,没有
3.分析变量声明,没有
4.分析函数声明,没有
执行 alert(b),发现 b.AO.b 不存在,则往上找 a.AO.b(此时 a.AO.b 为函数)
则第二次 alert 结果是 function(){alert(b);}
作用域
要学习 Clusure 必须了解 JavaScript 的作用域相关知识 作用域包括:
1.全局作用域
2.函数作用域
3.块级作用域(es6 新出,解决 var 问题, 新增 let, const)
var count = 100; //全局作用域 function foo1() { var count = 0; //函数全局作用域 return count; //返回函数 } if (count == 1) { //块级作用域 console.log(count); }
要注意一个函数作用域也是一个块级作用域,简单得来说,任何以 { }包裹的都是块级作用域
如果在一个作用域中访问一个变量或执行一个函数,js 脚本会从该作用域中寻找该对象是否存在,如不存在则向上级作用域寻找,则就形成作用域链
闭包
闭包是指有权访问另一个函数作用域中的变量的函数
先来一个简单的小 Demo
function t(){ var a = 20; return fucntion(){ console.log(a++); } } var a = 1000; var tmp = t(); tmp(); //输出结果 20
闭包是个非常抽象的概念,我的理解是这样的,通过以上词法分析的过程,我们知道,一个函数在调用前会生成一个作用域链,在内部作用域找不到的对象会向上层作用域寻找,即使这个函数已经调用结束之后,该作用域链并不会改变!
为了深入了解闭包,我们再看看一个复杂点的 Demo
for (var i = 0; i <= 5; i++) { setTimeout(function timer() { console.log(i); //猜猜会输出什么? }, 1000); }
答案是 6 6 6 6 6 6
因为 setTimeout 里面的回调函数是一个异步的过程,而 for 循环是同步的,立即执行,异步的 setTimeout 必须等待一秒才能执行,这时 i 早已经循环结束了.
有两种方式可以解决
1.用 let 替换 var. let 声明在块级作用域中,这个例子中是 for 循环 { } 中,而 var 则是在函数作用域内
2.可以使用一个匿名函数包裹要执行的异步函数 (注意,该例子还是会 1 秒钟输出 0-5,而不会每隔 1 秒输出一个数字)
for (var i = 0; i <= 5; i++) { log(i); // 0 1 2 3 4 5 } function log(i) { setTimeout(function () { console.log(i); }, 1000); }
以上是我对 javascript 词法分析,作用域链与闭包的个人理解,如有错误,还请提醒
![]()