JS函数作用域和闭包
JS函数作用域:是通过词法来划分的作用域,而不是动态地划分作用域的,这意味着,它们在定义他的作用域里运行,而不是在执行他的作用域里运行。当定义了一个函数,当前的作用域链就保存起来,并且成为函数的内部状态的一部份。
在最顶级作用域链仅由全局对象组成,而不和词法作用域相关,然而,当定义一个嵌套的函数时,作用域链就包括外面的包含函数。这意味着嵌套函数可以访问包含函数的所有参数和局部变量。尽管当一个函数定义时作用域链就固定了,但作用域链中定义的属性还没有固定。作用域链是活的,并且函数被调用时,可以访问任何当前的绑定。
调用对象:JS解释器调用一个函数时,它首先将作用域设为定义函数时起作用的那个作用域链。然后再在作用域的前面添加一个新的对象,这个对象叫调用对象。
任何用var语名声明的局部变量也都定义在对应的调用对象中,局部变量,函数参数 ,Arguments对象都在函数内的作用域中,而且调用对象位于作用域链的前端,所以它就隐藏了作用域链更上层的任何同名属性(如果是最顶层作用域链,那就是隐藏了同名全局变量)。也就是JS函数调用时局部变量为什么会覆盖全局变量的原因。
作为名字空间的作用域对象:如果代码中和其它的大多数的代码一样,定义变量来存储计算的中间结果,代码用在不同和程序中,就不知道它所创寻建的变量是否将会和导入它的程序所使用的变量发生冲突, 解决的办法就是把代码放在函数中,然后调用这个函数。
如:function init(){//…} ; init();这样就在全局名字空间添加了一个属性 init。如果不想添加属性可以这样:(function(){//…})();
作为闭包的嵌入函数:JS把函数将要执行的代码以及执行这些代码的作用域构成一个综合体,把这个综合体叫做闭包。 所有的JS函数都是闭包的,但是只有当嵌套函数被导出到它所定义的作用域外时,这种闭包才有趣,当一个嵌套函数以这种方式使用时,它常常明确地叫做一个闭包。
如果在函数fn中定义了函数fn2那么当函数fn2被调用时,作用链包括三个对象:
它自己fn2的调用对象,fn的调用对象,以及全局对象。如果嵌套函数fn2是在fn中调用的那么作用链中的所有对象都能被释放,当对象从作用域链中移除后,也就没有对它的引用了,最终通过对它的垃圾收集而完结。function fn(){ function fn2(){//…} ; fn2();}
如果把对嵌套函数的引用保存到一个全局作用域中,情况就不相同了。有两种方法可以实现:
1. 把嵌套函数作为外围函数的返回值
2. 把嵌套函数存储为某个其他对象的属性
在这种情况下有一个对嵌套函数的外部引用,并且嵌套函数将它的引用保留给外围函数的调用对象。结果是,外围函数的一次特定调用的调用对象依然存在,函数的参数和局部变量的名字和值在这个时候得以维持。(简单点说就是外围函数 return后它的函数的参数和局部变量的名字和值还仍然存在)
注:如果一个外围函数同时存储了两个函数的引用,那么这两个嵌入的函数就共享同一个调用对象,并且,一个函数的一次调用所做出的改变对于另一个函数的调用来说也是可见的。
使用第一种方式闭包的例子:
UniqueId = (function(){
var id = 0;
return function(){teturn id++;};
})();
使用第二种方式闭包的例子:(使用闭包的私有属性)
<script type="text/javascript">
function makeProperty(o , name , predicate){
var value ;
o["get" + name] = function(){ return value; };
o["set" + name] = function(v){
if(predicate && ! predicate(v))
throw "set"+name+": invalid value" +v;
else
value = v;
};
}
var o = {};
makeProperty(o , "Name" , function(x){ return typeof x =="string"; })
o.setName("jj");
alert(o.getName());
try{
o.setName(0);
}catch(e){
alert(e);
}
</script>