预编译以及作用域链
- js的预编译
- (1)函数预编译
- 案例及函数预编译过程
- (2)全局预编译
- 步骤:
- 作用域链
- 案例1
- 案例1执行过程
- 案例2
- 案例2执行过程
- 函数执行后AO不会销毁的情况
js的预编译
js完成解释执行分为三个步骤:1.语法分析;2.预编译(全局预编译、函数预编译);3.执行语句。
(1)函数预编译
案例及函数预编译过程
function fn(a) {
console.log(a);
var a=123;
console.log(a);
function a() {}
console.log(a);
var b = function() {}
console.log(b);
function d() {}
}
fn(1);
步骤分析:
1. 语法分析符号、大括号等语法检查;
2. 函数预编译
变量声明提升,function函数声明整体提升;发生在函数执行的前一刻
(实际过程如下):
(1) 创建AO对象--Activation Object(执行期上下文):AO{ };
(2) 找形参和变量声明,将变量和形参名作为AO属性名,即变量提升过程,值为undefined;
(3) 将实参的值放到形参中去
(4) 在函数体里面找函数声明,值赋予函数体
(1) 创建AO对象--Activation Object(执行期上下文):
AO{ };
(2) 找形参和变量声明,将变量和形参名作为AO属性名,即变量提升过程,值为undefined;
AO {
a:undefined,
b:undefined
}
(3)将实参的值放到形参中去
AO {
a:1,
b:undefined
}
(4)在函数体里面找函数声明,值赋予函数体
从(a属性在AO对象中已存在,故只增加d属性):
AO {
a:1,
b:undefined,
d:
}
到(给属性值赋予函数体):
AO{
a:function a() {},
b:undefined,
d:function d() {}
}
3.执行函数
(2)全局预编译
”全局“即从页内js的script 的开始标签到结束标签,从页外js文件的第一行到最后一行。
全局预编译过程与函数预编译过程大致相似,只是全局上无形参、实参的概念。
步骤:
1、生成一个GO对象--Global Object{},GO===window
2、变量提升
3、函数提升
案例:
console.log(a)
var a=123;
function a(){}
console.log(a)
过程:
1、GO{}
2、GO{
a:123
}
3、执行
a. 打印:function a(){}
b. GO{a:123}
c. 打印:123
作用域链
- 执行期上下文AO对象:当函数执行时,会创建一个称为执行期上下文的内部对象。一个执行期上下文定义了一个函数执行时的环境,函数每次执行时对应的执行上下文都是独一无二的,所以多次调用一个函数会导致创建多个执行上下文,当函数执行完毕,它所产生的执行上下文被 “销毁”。
执行期上下文指的就是函数执行前一刻所产生的AO对象
函数执行环境就是指变量提升函数提升得到的那些AO对象属性
function test() { }
函数多次调用,产生不同的AO对象:
test(); ---->AO{}
test(); ---->AO{}
函数执行完毕之后对应的AO对象销毁。
- [[scope]]:每个js函数都是一个对象,对象中有些属性我们可以访问,但有些不可以,这些属性仅供js引擎存取,[[scope]]就是其中一个。
function test() { }
我们可以访问的函数属性(如:test.length/test.prototype);
我们不能访问但着实存在的函数属性(如:test.[[scope]])
[[scope]]指的是我们所说的作用域,其中存储了运行期上下文的集合。
- 作用域链:[[scope]]中所存储的执行期上下文对象的集合,这个集合呈链式连接,我们把这种链式连接叫做作用域链。
案例1
function a(a) {
function b() {
var b=234;
}
}
var glob = 100;
a(123);
案例1执行过程
a函数被定义时发生如下过程:
a函数被执行时,发生如下过程:
b函数被创建时,发生如下过程:
也就是说b函数刚刚出生时所在的环境是a执行的结果,直接给b函数的出生创造好了环境。
b函数被执行时,发生如下过程:
b函数执行前一刻产生的AO对象放置在b.[[scope]]的最顶端。现在透彻地理解一下,在函数b中去访问变量时,是在b函数的作用域[[scope]]最顶端去查找变量。
案例2
function a() {
function b(){
function c() {
}
c();
}
b();
}
a();
案例2执行过程
a 定义: a.[[scope]] --> 0:GO
a 执行: a.[[scope]] --> 0:AO(a)
1:GO
b 定义: b.[[scope]] --> 0:AO(a)
1:GO
注意b执行了才会产生c的定义哈!!
b 执行: b.[[scope]] --> 0:AO(b)
1:AO(a)
2:GO
c 定义: c.[[scope]] --> 0:AO(b)
1:AO(a)
2:GO
c 执行: c.[[scope]] --> 0:AO(c)
1:AO(b)
2:AO(a)
3:GO
函数执行后AO不会销毁的情况
1 function a() {
2 var aaa=123;
3 function b() {
4 var bbb=234;
5 console.log(aaa);
6 }
7 return b;
8 }
9
10 var glob=100;
11 var demo=a();
12 demo();
b.[[scope]]不会被销毁。注意到上面例子中一个神奇的事情,在上例代码中的第7行,b函数被返回了。所以,在代码第11行,函数a被执行完毕后被返回的b函数最终存于变量demo中,b.[[scope]]不会被销毁。
上面这种情况就是闭包,总结一下,但凡是内部的函数被保存到了外部,那一定会形成闭包。