javascript--闭包
闭包是指有权访问另一个函数作用域中变量的函数。创建闭包的常见方式,就是在一个函数内部创建另一个函数。因此闭包就是一种编码现象!
function f1(){ var n=999; return function f2(){ alert(n); } }
上面的例子就是一个简单闭包。函数f2可以访问f1中的变量n,这是由于内部函数的作用域中包含了外部函数的作用域。下面我们将重点讲解函数作用域链与变量对象的关系,这对理解闭包至关重要。
一、作用域链与变量对象
当一个函数被调用时,会创建相应的作用域链、活动对象。在其作用域链中外部函数的活动对象始终处于第二位,外部函数的外部函数的活动对象处于第三位,......直至作用域链终点到全局执行环境。所以该函数的变量对象即其作用域链上所有活动对象和全局变量的总和。但是,作用域链本质上是一个指向变量对象的指针列表,它只引用但不实际包含变量对象!
var m=1; function f1(){ var n=999; alert(m+n); alert(x); //error return function f2(){ var x=2; alert(m+n+x); } } alert(n) //error
上面的例子中,各执行环境的变量对象如下:
- 全局:m;
- 函数f1:m、n;
- 函数f2:m、n、x;
变量、函数的作用域是永远不会改变的,因此若想跨作用域链访问变量就会出错。
二、全局环境访问局部变量
大家都知道函数都可以作为值赋值给变量,所以只要把子函数作为父函数的返回值,就能全局环境中访问到f2中的变量。
var m=1; function f1(){ var n=999; alert(m+n); return function f2(){ var x=2; alert(m+n+x); } } var f3=f1(); //1000 f3(); //1002
因此在全局环境中读取了f2中的代码。还有种方法也能达到同样的效果:
var m=1; function f1(){ var n=999; alert(m+n); //1000 (function f2(){ var x=2; alert(m+n+x); })() } f1(); //1000 1002
这是利用块级作用域在f1的执行环境中调用f2函数。
终上所述,闭包就是“函数内部定义的函数”,作用域链是由内向外直至全局环境。可以通过Javascript--特权方法在全局环境中访问局部作用域内的变量!
三、this对象
我们知道,this对象是在运行时基于函数的执行环境绑定的:在全局环境中,this对象为window。而当某个函数作为一个对象的方法被调用时,this对象就指哪个对象。请看下面这个例子:
var name="The Window"; alert(this.name) //"The Window"(在非严格模式下) var object={ name:"my object", getName:function(){ return this.name; } } alert(object.getName()) //"my object"
第一个this是在全局环境中执行的,this=window;第二个this对象是在定义的object中运行的,this=object。
但有时候由于编写闭包的方式不同,情况会有所不同。下面来看一个例子:
var name="The Window"; alert(this.name) //"The Window"(在非严格模式下) var object={ name:"my object", getName:function(){ return function (){ return this.name; } } } alert(object.getName()()) //"The Window"(在非严格模式下)
结果与上面的有所不同,问题就是对象方法中的匿名函数没有取得其包含作用域中的this对象?
每个函数被调用时会自动取得两个特殊变量:this和arguments。根据作用域链,内部函数只会搜索到其包含的活动对象,永远不可能直接访问外部函数中的this和arguments这两个变量。不过可以把外部函数的this对象保存在内部函数能访问到的变量中,就能让闭包访问this对象了。
var name="The Window"; alert(this.name) //"The Window"(在非严格模式下) var object={ name:"my object", getName:function(){ var that=this; return function (){ return that.name; } } } alert(object.getName()()) //"my object"