首页 > 代码库 > 【 js 基础 】作用域和闭包
【 js 基础 】作用域和闭包
var a = 2;
步骤三:代码生成
将 抽象语法树 (AST)转换为可执行代码。
然而对于解释型语言(例如JavaScript)来说,通过词法分析和语法分析得到语法树,没有生成可执行文件的这一过程,就可以开始解释执行了。
对于 var a = 2; 进行处理的时候,会有 引擎、编译器、还有作用域的参与。
引擎:从头到尾负责整个 Javascript 程序的编译及执行过程。
编译器:负责语法分析及代码生成等。
作用域:负责收集并维护由所有声明的标识符(变量)组成的一系列查询,并实施一套非常严格的规则,确定当前执行的代码对这些标识符(变量)的访问权限。
他们是这样合作的:
首先编译器会进行如下处理:
1、var a,编译器会从作用域中寻找是否已经有一个该名称的变量存在于同一个作用域的集合中。如果是,编译器会自动忽略该声明,继续进行编译;否则它会要求作用域在当前作用域的集合中声明一个新的变量,并命名为 a 。
2、接下来编译器会为引擎生成运行时所需的代码,这些代码用来处理 a = 2 这个赋值操作。引擎运行时会首先从作用域中查找 当前作用域集合中是否存在 变量 a。如果有,引擎就会使用这个变量。如果没有,引擎就会继续向上一级作用域集合中查找改变量。
然后 如果引擎最终找到了 变量 a,就赋值 2 给它。如果没有找到,就会抛出一个异常。
总结:
变量的赋值操作分两步完成:第一步 由编译器在作用域中声明一个变量(前提是之前没有声明过),第二步 是在运行时引擎会在作用域中查找该变量,如果可以找到,就对其赋值。
二、作用域
1、RL 查询
在上一部分我们说到了,引擎会对变量 a 进行查找。而查找分为两种,一是 LHS(Left-Hand-Side) 查询,二是 RHS(Right-Hand-Side) 查询。
LHS 查询:试图找到变量的容器本身,从而可以对其赋值。也就是查找 变量 a 。
RHS 查询:查找某个变量的值。查找变量 a 的值,即 2。
例子:
console.log(a); // 这里对 a 是一个 RHS 查询,找到 a 的值,并 console.log 出来。
a = 2; // 这里对 a 是一个 LHS 查询,找到 变量 a,并对其赋值为 2 。
function foo(a){ console.log(a); // 2 } foo(2); // 这里首先对 foo() 函数调用,执行 RHS 查询,即找到 foo 函数,然后 执行了 a = 2 的传参赋值,这里首先执行 LHS 查询 找到 a 并赋值为 2,然后 console.log(a) 执行了 RHS 查询。
function foo(a){ var b = a*2; function bar(c){ console.log(a,b,c); } bar(b*3); } foo(2); // 2,4,12
在这段代码中有三层作用域,如图所示嵌套:
window.a
foo(); function foo(){ console.log(a); var a = 2; }
学了上面的知识,你应该可以猜到 foo() 可以正常执行,而 console.log(a) 会打出 undefined; 原因是当把提升应用到上面代码,代码就相当于 下面的形式:
function foo() { var a; console.log(a); a = 2; } foo();
function foo() { var a; console.log(a); a = 2; }
函数表达式:
var foo = function() { var a; console.log(a); a = 2; } (function foo(){ var a; console.log(a); a = 2; })();
那么对于提升,来看个例子:
foo(); // 报TypeError错误 var foo = function() { var a; console.log(a); a = 2; }
这段代码相当于
var foo; foo(); // 此时 foo 为 undefined,而我们尝试对它进行函数式调用,属于不合理操作,报 TypeError 错误。 foo = function() { var a; console.log(a); a = 2; }
foo(); // 1 var foo; function foo(){ console.log(1); } foo = function(){ console.log(2); }
会输出 1 为不是 2,这段代码提升之后相当于:
function foo(){ console.log(1); } foo(); foo = function(){ console.log(2); }
foo(); // 3 function foo(){ console.log(1); } var foo = function(){ console.log(2) } function foo(){ console.log(3) }
function foo(){ var a = 2; function bar(){ console.log(a); } return bar; } var baz = foo(); baz(); //2
function wait(message){ setTimeout(function timer(){ console.log(message) },1000) } wait("hi");
function foo(){ var a = 2; function baz(){ console.log(a); // 2 } bar(baz); } function bar(fn){ fn(); //闭包 }
for (var i=0;i<=5;i++){ setTimeout(function timer(){ console.log(i) },i*1000) }
for (var i=0;i<=5;i++){ (function(i){ setTimeout(function timer(){ console.log(i) },i*1000) })(i) }
【 js 基础 】作用域和闭包