JS运行三部曲
我们都知道javascript是解释型语言,执行的特点呢是编译一行,执行一行。按照这个思路有时候我们在运行代码时会有一些令人费解的现象出现。下面我们一起来执行下面三段代码。
<script> var a = 123; console.log(a); </script>
<script> console.log(a); </script>
<script> console.log(a); var a = 123; </script>
运行上面三段代码可以得出结果分别为:123.和a is not defined.和undefined.
按理说第三个代码应该也输出:a is not defined.但是并没有,这正是由于javascript在执行代码前的预编译产生的。
前面说的是变量的例子,下面我们看看函数执行时的效果。请看下面两段代码。
<script> function test() { document.write("a"); } test(); </script>
<script> test(); function test() { document.write("a"); } </script>
运行上面两段代码执行结果都为:a。这也是预编译所产生的结果。
这又是为什么呢。下面我们带着疑问来了解一下预编译到底是什么。
全局变量、暗示全局变量和局部变量的概念
在这之前我们先来了解一下什么是:暗示全局变量。我们都知道变量有两种类型:全局变量和局部变量。在函数外部定义的就叫全局变量,在函数内部定义的就叫局部变量。
先看一段代码
<script> var a = 1; b = 2; function test() { var c = d = 3; //变量的赋值是从右到左的,相当于:d = 3; var c = d; console.log(c); console.log(d); } test(); console.log(b); console.log(d); </script>
执行结果为:3323.
奇怪,d 不是局部变量吗,为什么可以在全局进行访问?为什么 b = 2 这样也可以声明变量 ?
那么就只有一个可能,d 不是局部变量,而是暗示全局变量。
接下来我们熟悉一下两个重要的概念。
- imply global暗示全局变量:即任何变量,如果变量未声明就赋值,此变量为全局对象(window)所有。window就是全局的域。
- 任何声明的全局变量,皆为window的属性。所以访问一个全局变量可以console.log(a),也可以console.log(window.a).
所以出现上面的结果也就不难解释了,a 是全局变量。b d二者是暗示全局变量(即未声明就赋值的变量)。c 是局部变量。所以变量d可以在外部进行访问。以后我们只要记得暗示全局变量也是全局变量即可。
JS运行三部曲
下面,我们正式进入JS运行三部曲部分。
1、语法分析
2、预编译
3、解释执行
在执行代码前还有两个步骤,即语法分析和预编译。
在预编译之前,系统会对整个代码全部扫描一遍,看看有没有低级的语法错误,如少了括号或引号等等。解释执行就是从上到下依次执行函数代码。
下面我们重点来讲一下预编译。讲完预编译后相信大家就会知道之前的代码执行结果为什么是那样的啦。
我们分函数体内的预编译和全局预编译两个部分来讲解预编译。
函数体内的预编译
函数体内预编译四部曲//发生在函数执行的前一刻。
- 创建AO对象。Activation object。即作用域,或叫执行期上下文。
- 找形参和变量声明,将变量和形参名作为AO的属性名,初始值为undefined.
- 将形参和实参相统一。
- 在函数体里找函数声明,值(函数声明)赋予AO对象。
注意:预编译过程只涉及变量或函数的声明,赋值语句在解释执行的阶段才进行。
我们来看一下这段代码具体解释一下函数体内预编译的详细过程,也可以自己先执行一遍,看一下结果与自己预期的是否一致。
<script> function fn(a){ //定义一个函数fn() console.log(a); var a = 123; console.log(a); function a() {} //函数的声明在预编译时已经被识别,所以在调用fn(1)时,忽略这条语句 console.log(a); var b =function () {} console.log(b); function d() {} } fn(1); </script>
1、创建一个AO对象。AO {}。
2、找形参和变量声明,将变量和形参名作为AO的属性名,初始值为undefined。变量名和形参名一致的话只写一个就行。
AO { a : undefined
b : undefined
}
3、将实参和形参统一。
AO {a : 1
b : undefined
}
4、在函数体里找函数声明,值(函数声明)赋予AO对象。
AO {a :function a() {}
b : undefined
d : function d() {}
}
预编译结束后,才执行fn(1)函数。(执行过程为从上到下依次执行)
执行结果为:function a() {} 、123、123、function () {}.
全局预编译
全局预编译(整个script代码块执行前)
1、创建一个GO对象。GO对象即window对象。
2、找变量声明。初始值为undefined。注:全局预编译没有形参和实参
3、找‘全局里的函数声明,函数声明赋值到全局里的GO。
我们来看一下下面这段代码来了解一下全局预编译。
<script> function b(a) { console.log(a); } b(1); console.log(a); var a = 2; console.log(a); </script>
1、创建一个GO对象。 GO { }
2、找变量声明GO {a : undefined}
3、找函数声明GO {a : undefined
b : function b(a){...} }
从上到下执行整个代码。
执行到 b (1)时,先进行函数体的预编译,然后再执行 b (1)函数。
1、创建一个AO对象AO { }
2、找形参和变量声明AO {a : undefined}
3、将实参和形参统一AO {a : 1}
4、在函数体里找函数声明,值(函数声明)赋予AO对象。没有函数体,则这一步忽略。
预编译完成后执行 b(1)函数。
最后显示的结果为:1undefined2.
总结:JS运行过程
全局预编译(脚本代码块script执行前)
- 查找全局变量声明(包括隐式全局变量声明,省略var声明),变量名作全局对象的属性,值为undefined 查找函数声明,函数名作为全局对象的属性,值为函数引用
- 查找函数声明,函数名作为全局对象的属性,值为函数引用。
从上往下依次执行代码,遇到函数体时,进行函数体预编译。
函数体预编译(函数执行前)
- 创建AO对象(Active Object)
- 查找函数形参及函数内变量声明,形参名及变量名作为AO对象的属性,值为undefined
- 实参形参相统一,实参值赋给形参
- 查找函数声明,函数名作为AO对象的属性,值为函数引用
函数体预编译后,继续往下执行代码。