一次性搞懂JavaScript闭包

96
LuckyJing
0.1 2015.09.05 11:33* 字数 1038

引言:JavaScript中的闭包和离散数学中的闭包并没有任何关系。
The use of the word closure here comes from abstract algebra, where a set of elements is said to be closed under an operation if applying the operation to elements in the set produces an element that is again an element of the set. The Lisp community also (unfortunately) uses the word closure to describe a totally unrelated concept.

作用域

简单的说,作用域就是变量与函数的可访问范围,即作用域控制着变量与函数的可见性和生命周期。在JavaScript中,变量的作用域有全局作用域和局部作用域两种。

全局作用域
var num1 = 1;
function fun1 (){
    num2 = 2;
}

以上三个对象num1,num2fun1均是全局作用域,这里要注意的是末定义直接赋值的变量自动声明为拥有全局作用域

局部作用域
function wrap(){
    var obj = "我被wrap包裹起来了,wrap外部无法直接访问到我";
    function innerFun(){
        //外部无法访问我
    }
}

作用域链

Javascript中一切皆对象,这些对象有一个[[Scope]]属性,该属性包含了函数被创建的作用域中对象的集合,这个集合被称为函数的作用域链(Scope Chain),它决定了哪些数据能被函数访问。

function add(a,b){
    return a+b;
}

当函数创建的时候,它的[[scope]]属性自动添加好全局作用域

函数创建时的作用域链

var sum = add(3,4);

当函数调用的时候,会创建一个称为运行期上下文(execution context)的内部对象,z这个对象定义了函数执行时的环境。它也有自己的作用域链,用于标识符解析,而它的作用域链初始化为当前运行函数的[[Scope]]所包含的对象。

函数执行时

在函数执行过程中,每遇到一个变量,都会经历一次标识符解析过程以决定从哪里获取和存储数据。该过程从作用域链头部,也就是从活动对象开始搜索,查找同名的标识符,如果找到了就使用这个标识符对应的变量,如果没找到继续搜索作用域链中的下一个对象,如果搜索完所有对象(最后一个为全局对象)都未找到,则认为该标识符未定义。

闭包

闭包简单来说就是一个函数访问了它的外部变量。

var quo = function(status){
    return {
        getStatus: function(){
            return status;
        }
    }
}

status保存在quo中,它返回了一个对象,这个对象里的方法getStatus引用了这个status变量,即getStatus函数访问它的外部变量status;

var newValue = quo('string');//返回了一个匿名对象,被newValue引用着
newValue.getStatus();//访问到了quo的内部变量status

假如并没有getStatus这个方法,那么quo('sting')结束后,status自动被回收,正是因为返回的匿名对象被一个全局对象引用,那么这个匿名对象又依赖于status,所以会阻止status的释放。

例子
//错误方案
var test = function(nodes){
    var i ;
    for(i = 0;i<nodes.length;i++){
        nodes[i].onclick = function(e){
            alert(i);
        }
    }
}

匿名函数创建了一个闭包,那么其访问的i是外部test函数中的i,所以每一个节点实际上引用的是同一个i。

错误的结果
//改进方案
var test = function(nodes){
    var i ;
    for(i = 0;i<nodes.length;i++){
        nodes[i].onclick = function(i){
            return function(){
                alert(i);
            };
        }(i);
    }
}

每一个节点绑定了一个事件,这个事件接收一个参数,并且立即运行,传入i,因为是按值传递的,所以每一次循环都会为当前i产生一个新的备份。

正确使用后的测试

思考题目

转载自阮一峰博客,出自《JavaScript高级程序设计》

题目一
 var name = "The Window";
  var object = {
    name : "My Object",
    getNameFunc : function(){
      return function(){
        return this.name;
      };
    }
  };
  alert(object.getNameFunc()());

运行结果:The Window

解释:object.getNameFunc()这是属于方法调用,this指针指向的是object,可以用一个变量tmp引用它的结果,实际上tmp就是这个方法返回的那个匿名函数function(){return this.name;};,此时并没有执行内部代码,执行tmp()时,也就是object.getNameFunc()()时,属于函数调用(另一篇博文详解了这里,链接),this指针指向window,最终返回The Window

题目二
var name = "The Window";
  var object = {
    name : "My Object",
    getNameFunc : function(){
      var that = this;
      return function(){
        return that.name;
      };
    }
  };
  alert(object.getNameFunc()());

运行结果:My Object

解释:在调用getNameFunc()时,属于方法调用,那么this指针指向object,把它被that引用,那么返回的匿名函数中时刻保持对object的引用,很好理解。

JavaScript
Web note ad 1