预编译以及作用域链

  • 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函数被定义时发生如下过程:

JavaScript 编译成 java代码 js编译过程_javascript


a函数被执行时,发生如下过程:

JavaScript 编译成 java代码 js编译过程_作用域链_02


b函数被创建时,发生如下过程:

JavaScript 编译成 java代码 js编译过程_typescript_03


也就是说b函数刚刚出生时所在的环境是a执行的结果,直接给b函数的出生创造好了环境。

b函数被执行时,发生如下过程:

JavaScript 编译成 java代码 js编译过程_预编译_04


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]]不会被销毁。
上面这种情况就是闭包,总结一下,但凡是内部的函数被保存到了外部,那一定会形成闭包。