Javascript学习之一 - 施行时的准备工工作
Javascript执行时的准备工作:
当JS引擎进入一个执行环境,准备执行该区域的代码时,如下工作已经完成:(1) 变量的实例化,(2) 作用域链的创建和初始化,(3) this指针所指向的目标对象的确定。
1、执行环境(Execution Contexts)
一段可执行的JS代码即为一个执行环境,分为三类:
1.1 全局代码;全局代码是不包含任何函数体的代码(可以有函数调用);
1.2 Eval代码;eval是JS中的一个内置函数,eval的参数即全局代码;
1.3 函数代码;函数代码是函数体中的一部分代码,这部分代码不嵌套包括任何函数体代码。注意new Function(argList)最后一个参数也是当作函数代码处理。
2、变量实例化(Variable Instantiation )
JS的每个执行环境(也叫执行上下文)都关联到一个变量对象(variable object)。变量实例化将做以下事情:
2.1 如果该执行环境是函数代码的执行环境,每一个形参作为属性添加到变量对象中,属性名为形参名,值是调用该函数时所提供的实参的值。如果实参少于形参,则多出的形参赋值为undefined。如果有形参中有两个或者两个以上的参数的名字相同,则最后一个形参的值是变量对象中该形参的值,即使最后一个形参为undefined。
2.2 把函数定义式定义的函数(不是函数表达式定义的函数)作为属性添加到变量对象中。属性名为函数名,值为通过new Function(参数列表) 创建的一个函数对象,该对象的属性由代码决定(执行的时候确定)。如果变量对象中已经存在该名字的属性,则用新的值和属性替换旧的值和属性。
如果执行环境是函数代码,必须先初始化函数的形参列表,然后再处理该步骤。
2.3 把通过关键字var定义的变量(该变量也可以是源码某个语句块中定义的,如:for语句循环体中定义的)作为属性添加到变量对象中,属性名为变量名,值为undefined,值的属性由代码确定(执行的时候确定);如果该变量名和函数定义或者形参重合,则不摈弃变量对象中该属性的已有属性。
语义上,必须先初始化函数形参列表和函数定义,再处理该步骤。
实验1(环境Chrome 8 ):
<script language="javascript"> var fun; function fun(a, b, a){ alert(a); } fun(1, 2, 3);//弹出:3 fun(1, 2);//弹出:undefined var fun = 'not function'; fun(1);//出错 </script>
3、作用域链(Scope Chain)和变量识别(Identifier Resolution)
每个执行环境都与一个作用域链关联。作用域链是一个对象列表,主要用于变量识别(可以理解为它由赋值后的变量对象以链表的形式构成)。系统按照以下步骤来计算一个变量 v的值:
4、全局变量(global object)
5、活动对象(activation object)
当JS执行引擎跳转到一个函数代码执行环境时,将创建一个活动对象与执行环境关联,活动对象将拥有一个名为arguments的属性,其值为存放函数参数的一个arguments对象。该活动对象将作为执行函数代码的变量对象。
此时的活动对象被当作变量对象来执行变量实例化。
活动对象是一种特殊的实现机制,JS程序只能访问活动对象的成员,不能访问活动对象本身。
6、arguments对象(Arguments object)
当JS跳转到函数代码执行环境时,将创建一个arguments对象,该对象有如下特点:
(1) arguments对象的prototype原型为Object对象的原型对象,该原型对象的值为即Object.prototype;
(2) arguments有一个callee属性,其初始值为被执行的函数对应的函数对象(即函数自身),该特性使得匿名函数也可以递归调用。
(3) arguments有一个length属性,用来告诉实参的个数;
(4) 对于实参将依次放入arguments[0], arguments[1], ... , arguments[n];
图:实验1中第一次执行函数fun时arguments对象属性
7、预编译(预处理)
8、代码的执行
Step 1: 创建作用域链,此时的作用域链中仅有一个对象全局的window对象;
8.2 函数代码的执行
8.3 Eval代码的执行
代码分析一
<script> var var1; var2 = 'var2'; fun1(); function fun1(){ alert('fun1'); var3 = 'var3'; } var var4 = function(){ alert('匿名函数'); }; var var5 = function fun2(){ alert('fun2'); }; for(var i=0; i<10; i++){ var var6 = i*i; } var var6; var5(); fun3(); var4(); </script> <script> var var7; fun3(); function fun3(){ alert('fun3'); } </script>
- i, fun1, var1, var4, var6都作为属性添加到变量对象window中。其中fun1的值为一个Function对象,其它变量的值都是undefined。
- 变量i是在for语句结构中定义的, var6是在for语句循环体中定义的,此时都已添加;
- var7, fun3并没有添加,因为它在另外一个script标签中,var2也没有添加,因为它没有通过关键字 var 来定义
- var4对应的匿名函数表达式和var5对应的有名函数表达式都没有添加到window中,因为它们是函数表达式,执行的时候才进行计算。var4和var5的值为undefined,而fun1的值不是undefined;
- 作用域链为: [window]
(2) 执行第3行时,因为程序对一个没有定义的变量var2赋值,系统将隐式的定义一个变量var2,并作为属性添加到window中;第3行执行结束后window.var1 = 'undefined'; window.var2 = 'var2';
(3)执行第4行时,将在当前作用域链中,进行一次变量识别,在变量对象window.fun1中找到了fun1的值,并执行fun1。
(4) 第9行执行完后,window.var4的值为:通过匿名函数表达式创建的一个Function对象。
(5) 执行第13行后window.var5的值为:通过有名函数表达式创建的一个Function对象。但是此时window对象中并不存在fun2这个属性;
(6) 执行完第19行后,window.var6赋值为81;
(7) 执行完第20行后,window.var6仍然是81,因为这只是一个变量定义语句;
(8) 执行第22行时出错,因为在window中找不到名为fun3的属性,故返回null.fun3,试图计算null.fun3()时报错;
(9) 在一个执行块(script)中出错后,该块中的后续JS将不再执行,故var4()不会执行;但是后面script标签中块将继续执行
(10) 执行第26行之前,此时会预处理该script标签中的JS源码,流程和处理第一个script标签相似。
(11) 执行第27行时将弹出fun3。