了解闭包

江湖上都说要了解闭包,得先了解作用域链,所以,先从作用域链开始吧。

作用域链

  1. 作用域链是一个对象列表或链表,这组对象定义了这段代码“作用域”中的变量;
  2. 每当调用一个函数,这个函数会创建一个新的对象来储存它的变量(变量绑定对象),并且将这个对象添加到作用域链上;当函数返回时,就从作用域链中将这个对象删除;
  3. 当javascript需要查找一个变量时,它会沿着作用域链一级一级地搜索变量。搜索过程始终从作用域链的前端开始,然后逐级地向后回溯,直至作用域链的最顶层(全局对象)为止。
  4. 对于嵌套函数,每个嵌套的函数都各自对应一个作用域链,并且这个作用域链都指向一个变量绑定对象

我们来看一个栗子

1
2
3
4
5
6
7
8
9
10
var word = " the window"
function sayWord(){
var word = "sayWord"
function sayHello(){
var word = "sayHello"
alert(word)
}
return sayHello
}
sayWord()();

上例的作用域链就是:
sayHello[word="sayHello"]——sayWord[word="sayWord"]——window[word="the window"];
当执行sayHello函数时,会沿着这个作用域链一级一级往上找word这个变量,直到找到为止。

闭包

javacript高级程序设计上说“有不少开发人员总是搞不清匿名函数和闭包这两个概念”。很遗憾,本人就是。

@javacript高级程序设计

闭包是指有权访问另一个函数作用域中的变量的函数。

@xiaotie

闭包是从用户角度考虑的一种设计概念,它基于对上下文的分析,把龌龊的事情、复杂的事情和外部环境交互的事情都自己做了,留给用户一个很自然的接口。

@javacript权威指南

函数对象通过作用域链相互关联起来,函数体内部的变量都可以保存在函数作用域内


上栗子

1
2
3
4
5
6
7
8
9
var word = " the window"
function sayWord(){
var word = "sayWord"
function sayHello(){
alert(word)
}
return sayHello
}
sayWord()();

sayHello函数在sayWord函数内部,它能访问sayWord函数内部的变量。sayHello函数就是闭包,

利用闭包实现私有属性

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
function createCounter() {
var counter = 0;
function increment() {
counter = counter + 1;
console.log("Number of events: " + counter);
}
return increment;
}
var counter1 = createCounter();
var counter2 = createCounter();
counter1(); // Number of events: 1
counter1(); // Number of events: 2
counter2(); // Number of events: 1
counter1(); // Number of events: 3

每次调用函数都会创建变量绑定对象添加到作用域链中,所以每次调用外部函数的时候,作用域链都是不同的。而对于嵌套函数,每次调用外部函数时,内部函数又会重新定义一遍。

闭包存在的问题

this对象的指向问题

1
2
3
4
5
6
7
8
9
10
11
var name = "The Window";
var object = {
name : "My Object",
getNameFunc : function(){
var name = "The v";
return function(){
return this.name;
};
}
};
alert(object.getNameFunc()()); //"The Window"(在非严格模式下)

每个函数在被调用时都会自动取得两个特殊变量:thisarguments。内部函数在搜索这两个变量时,只会搜索到其活动对象为止,因此永远不可能直接访问外部函数中的这两个变量。

这里

object.getNameFunc()()==(function(){return this.name;})()

所以其活动对象为window;

解决办法

1
2
3
4
5
6
7
8
9
10
11
var name = "The Window";
var object = {
name : "My Object",
getNameFunc : function(){
var that = this;
return function(){
return that.name;
};
}
};
alert(object.getNameFunc()()); //"My Object"


内存泄露问题

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
function sayWord(){
var word = "hello"
add = function(){
word = word + " world"
}
function sayHello(){
alert(word)
}
return sayHello;
word = null;
}
var say = sayWord();
say(); //hello
add();
say(); //hello world

  • 首先调用say();结果输出hello
  • 然后调用add,add是个全局变量,所以可以在外部调用,因为它又是闭包,所以可以访问到变量word,所以world=“hello world”;
  • 最后再调用say();发现结果输出hello world;
    这说明函数sayWord中的局部变量word一直保存在内存中,并没有在sayWord调用后被自动清除。产生这个问题主要是由于匿名函数保存了一个对word的引用,所以它所占用的内存就永远不会被回收。

注意:因为闭包的这个特性,所以外部函数的变量是其内部所有闭包的共享值,因此,不能在闭包中随意的改变外部函数的变量值,牵一发而动全身。