JavaScript 引擎在代码执行前会进行词法分析,词法分析主要有三个步骤:

分析参数 -> 分析变量的声明 -> 分析函数声明

词法分析过程

JavaScript 函数在运行的瞬间,会生成一个活动对象 AO(Active Object),举个例子

1
2
3
4
5
6
7
function f1(name){
var name = 'mary';
console.log(name);
function name(){}
console.log(name);
}
f1("abc");

第一步:分析参数:

  1. 函数接收形式参数,添加到 AO 的属性,并且这个时候值为 undefined, 即 AO.name=undefined
  2. 接收实参 "abc",添加到 AO 的属性,覆盖之前的 undefined,即 AO.name='abc'

第二步:分析变量声明:var name;var name='mary';

  1. 如果上一步分析参数中 AO 还没有 name 属性,则添加 AO 属性为 undefined,即 AO.name=undefined
  2. 如果 AO 上面已经有 name 属性了,则不作任何修改,此时 AO.name==='mary'

第三步:分析函数的声明:

如果有 function name (){} 把函数赋给 AO.name , 覆盖上一步分析的值

实例

分析下面这个栗子:

1
2
3
4
5
6
7
8
9
var a = 10;
function test(a){
console.log(a); //function a (){}
var a = 20;
console.log(a); //20
function a (){}
console.log(a); //20
}
test(100);

第一步,分析函数参数:

1
2
AO.a = undefined // 形式参数 
AO.a = 100 // 接收实参

第二步,分析局部变量:

1
 第 4 行代码有 var a, 但是此时已有 AO.a = 100, 所以不做任何修改,即 AO.a = 100

第三步,分析函数声明:

1
 第 6 行代码有函数 a, 则将 function a (){} 赋给 AO.a, 即 AO.a = function a (){}

执行代码时:

1
2
3
4
5
 第 3 行代码运行时拿到的 a 时词法分析后的 AO.a,即 AO.a = function a (){};
第 4 行代码:将 20 赋值给 a, 此时 a=20;
第 5 行代码运行时 a 已经被赋值为 20, 结果 20;
第 6 行代码是一个函数表达式,所以不做任何操作;
第 7 行代码运行时仍是 20;

ps:

1
2
3
4
5
6
7
8
1.var a = 10;
2.function test(a){
3. var a; // 证明词法分析第二步。
4. alert(a); //100
5. a = 20;
6. alert(a); //20
7.}
7.test(100);

ps:

1
2
3
4
5
6
7
8
9
var a = 10;
function test(a){
alert(a); //100
var a = 20;
alert(a); //20
a = function(){} // 是赋值,只有在执行时才有效
alert(a); //function (){}
}
test(100);

ps:(执行结果同上)

1
2
3
4
5
6
7
8
9
var a = 10;
function test(a){
alert(a); //100
var a = 20;
alert(a); //20
var a = function(){} // 是赋值,只有在执行时才有效
alert(a); //function (){}
}
test(100);

补充说明:函数声明与函数表达式

1
2
3
4
5
6
// 函数声明 
function a(){
}
// 函数表达式
var b = function(){
}

a 和 b 在词法分析时,区别:

1
2
a 在词法分析时,就发挥作用;
b 只有在执行阶段,才发挥作用。

词法作用域

词法作用域的意思是,作用域为在定义时 (词法分析时) 就确定下来的,而并非在执行时确定。即在函数未执行前,函数执行的顺序已经被确定,而不是类似 JAVA 一样,是在执行前根本不知道执行顺序。