要理解javascript函数的定义与执行,首先需要知道这几个重要的概念,现在可以先知道稍后再理解!
函数的执行环境(excution context)、活动对象(call object)、作用域(scope)、作用域链(scope chain)。
接下来,我们以这个函数为例进行分析:
步骤:
1、设置作用域链
当定义函数a的时候,JS解释器会将函数a的作用域链(scope chain)设置为“定义a时a所在的环境”,此处a第一个添加的作用域是window对象。(如果a是一个全局函数,则scope chain中只有window对象。)
个人见解:作用域链里面其实是包含的活动对象,活动对象可以理解为是用来识别作用域的。(就像是一个商场分为A、B、C 三个区,就可以理解为在这个商场的作用域链里面,有A、B、C 3个活动对象,3个范围!(“个人见解,可能不恰当”)。
2、执行环境
当执行函数a的时候,a会进入相应的执行环境(excution context)。
个人见解:创建执行环境分为创建作用域和创建活动对象两步。
3、作用域
当创建执行环境的过程中,首先会为a 添加一个scope属性,即a的作用域,其值就是第一步中的scope chain。即a.scope = a的作用域链。
个人见解:可以把作用域和作用域链理解成是名字不同但作用相同!
4、创建活动对象
创建完作用域,紧接着执行环境会创建一个活动对象(call object)。活动对象也是一个拥有属性的对象,但它不具有原型而且不能通过javascript代码直接访问(可以看下面的图或者个人见解理解)。创建完活动对象后,把活动对象添加到a的作用域链的最顶端。此时a的作用域链包含两个对象:a的活动对象和window对象。
下一步是在活动对象上添加一个arguments属性,它保存着调用函数a时所传递的参数。
最后所有的函数a的形参和内部的函数b的引用也被添加到a的活动对象上。在这一步中,完成了函数b的定义,因此如同第3步,函数b的作用域被设置为b所定义的环境,即a的作用域。(就类似于a的作用域链中第一个加入的作用域是window对象。)!
个人见解:(1) 活动对象是一个为了理解而添加的名词,实际不存在,所以不具有原型、也不能用实际代码访问。(类似于磁感线,只是为了描述)
完结:到此,整个函数a从定义到执行的步骤就完成了。此时a返回b的引用给c,又因为函数b的作用域链包含了函数a的活动对象的引用,也就是说b中可以访问到a中定义的所有变量和函数。又函数b被c引用,函数b依赖于a,因此函数a再返回后不会被GC回收(参考最下方javascript的垃圾回收机制)!
当函数b被执行的时候也会像以上步骤一样。因此执行时b的作用域链包含了3个对象:b的活动对象,a的活动对象,window对象,如图所示:
如图所示,当在函数b访问一个变量的时候,搜索顺序是:
b的活动对象 —>b的原型对象(存在的话)—>a的活动对象 —>window对象
变量查找机制:先查找自身的活动对象,如果存在则返回,如果不存在且该函数存在prototype原型对象,则查找原型对象。依次查找a的活动对象、window对象。直到找到为止,如果整个作用域链上没有找到,则返回undefined。
总结:以上提到了两个重要的词语:函数的定义与执行。文中提到函数的作用域是在定义函数的时候就已经确定,而不是在执行的时候确定(参看步骤1和3).用一段代码来说明这个问题:
1 function f(x){ 2 var g = function(){ return x;} 3 return g; 4 } 5 var h = f(1); 6 alert(h());
这段代码中变量h指向了f中的那个匿名函数(由g返回,也可以理解为不匿名,因为有名字g(不正规理解))。
(1)h的作用域在定义的时候确定:
个人理解h的作用域链:g的活动对象->f的活动对象->window对象(因为此处h代表的是返回的"g()"函数,既然是定义为准,就应该是定义"g()"函数时确定,所以 h.scope chain = g.scope chain )
网上参考h的作用域链:h的活动对象 ->f的活动对象->window对象
(2)h的作用域在执行(alert( h ( ) )的时候确定:
h的作用域链:h的活动对象->alert的活动对象->window对象。
结果:(1)应该输出1 (2)应该输出undefined 。
实践证明函数的作用域是在定义这个函数的时候就已经确定了!
补充:Javascript的垃圾回收机制
在Javascript中,如果一个对象不再被引用,那么这个对象就会被GC回收。如果两个对象互相引用,而不再被第3者所引用,那么这两个互相引用的对象也会被回收。因为函数a被b引用,b又被a外的c引用,这就是为什么函数a执行后不会被回收的原因。
function a(){
var i = 0;
var b = function(){ alert(++i);}
return b;
}
var c = a();
c();
以上代码如果没有return b; 当函数a执行完后就会被回收!两个对象互相引用(a引用b,b引用a),而不再被第3者所引用(没有返回数据给c),那么这两个互相引用的对象也会被回收。
参考文档:http://www.jb51.net/article/24101.htm (javascript深入理解js闭包)