闭包

JavaScript备忘录(2)——闭包

 

语句

JavaScript是解释型语言,解释器是按照顺序逐句执行的(除了进行一些少量预处理,如将函数声明提前)。

顺序是由流程控制语句来控制的,常用的流程控制语句包括:

  • 条件控制语句:if...else和switch...case语句
  • 循环控制语句:while...、do...while、for和for-in语句
  • 其他:break、continue和try...catch语句

代码中除去流程控制语句,剩下的部分只做两件事:

  • 为变量赋值(声明新变量并复制如var obj = {word: "hello"},或为已有变量赋值x=y)
  • 调用函数,比如console.log("hello world")

实际上为了完成以上两件事,代码还需要编写值或对象的字面量,以及访问现有变量,但是单独这样做没有意义。

函数执行过程

上面说到,除去流程控制,代码只做两件事,为变量赋值和调用函数。前者不难理解,现在来看调用函数后发生了什么:

当解释器调用一个函数之后,就会创建与之对应的变量对象(注意,一个变量对象与一次函数调用对应,而不是与一个函数对应),函数内声明的所有局部变量都绑定在这个变量对象上,函数执行完毕后,不出意外,该变量对象就会销毁。

我们将函数执行过程中所有可以访问到的变量的集合称为函数的执行上下文。对C++来说,函数的执行上下文只有函数内部定义的变量、函数实参和全局变量,而JavaScript的最大特点在于,函数的执行上下文包括了函数调用者的执行上下文,也就是说在函数内部可以毫无障碍地访问函数外部的数据。在我们什么都没做的时候,其实已经处在了全局的执行上下文之中了。

var names = [];
function sayhi(name){
if(!names[name]){
names.push(name);
} console.log("hello "+name);
} sayhi("Ross");
sayhi("Rachel"
);
console.log(names); //-> ["Ross", "Rachel"]
console.log(name); //-> undefined

上面函数sayhi执行过程中,代码试图访问names变量,但是在函数内部并未定义names(GCC这时就要报错了),所以函数就去sayhi的调用者(window)的变量对象上找到了names。

当函数内部调用其他函数(或自己)时,变量对象就变成了三个或者更多,当在函数内试图访问某个变量时,就会从内向外依次在变量对象中查找变量,直到找到第一个变量为止。

闭包

上文说到,函数执行完毕后,不出意外,该变量对象就会销毁。实际上,经常会出现意外。如果函数执行完毕后,仍然有可能访问到函数内部的变量,此时虽然执行流程已经在函数外部了,但函数的变量对象不会被销毁

var last;
function sayhi(name){
    last = function(){
        return name;
    }
    console.log("hello "+name);
}
sayhi("Ross");
console.log(last()); //-> "Ross"

 由于在sayhi函数内部,我们将外部变量last赋值为了一个新的函数,该函数返回sayhi函数的实参name,所以sayhi("Ross")之后,虽然sayhi已经执行完成了,但是last仍然可以访问到sayhi内部的变量name,所以由sayhi("Ross")这次调用带来的变量对象不会被销毁。而且,只要接下来的代码(解释器永远不知道下一行代码是什么)有可能访问到该变量对象中的元素,该变量对象就不会销毁。

变量对象未销毁,其包含的所有元素(包括那些不可能再被访问到的元素)也会保留。所以,如果使用不当,闭包会超出预期限度地加重浏览器的负担。(现在已有一些浏览器如Chrome开始尝试回收闭包中的那些不可能再被访问到的元素)。

闭包可以用来模拟对象的私有变量。下面这个对象对象chandler模拟了C++中的类的实例,两个属性name和hobby只能通过方法访问,不能被改变。

var chandler = (function(){
    var name = "CHANDLER";var hobby = "joke";return {
        getName : function(){return name;},
        getHobby : function(){return hobby;}
    }
})();

你可能感兴趣的:(闭包)