闭包

作者 Zwe1 日期 2018-03-21
闭包

MDN:闭包是函数和声明该函数的词法环境的组合。

要想直接通过闭包的定义来理解这个功能十分困难,不如我们先来看看用它来解决什么问题,有怎样的用途?

提升变量作用域

在javascript语言中,存在一种“链式作用域”的语言环境。子环境会一级级向上寻找父环境的变量。父环境的所有变量,对子环境透明可见,反之则不成立。

1
2
3
4
5
6
7
8
9
10
11
12
var a = 1;
var fn = function () {
var b = 2;
function getVariable () {
console.log('a and b:', a, b);
};
getVariable();
};
fn();
// a and b: 1 2
console.log('getB:', b);
// ReferenceError: b is not defined

javascript存在两种作用域环境,一种是全局作用域,一种是函数级作用域。上面例子中可见三层作用域,分别是全局作用域,fn函数作用域,getVariable函数作用域,内层作用域可访问外层作用域变量,反之不可。

那么当我们在外层作用域想访问内层作用域定义的变量时,该怎么办呢?使用闭包便可以解决这一问题。

1
2
3
4
5
6
7
8
9
var fn = function () {
var b = 2;
return function () {
console.log('b:', b);
};
};
var getB = fn();
getB();
// b: 2

在函数fn中返回一个函数,使用内层作用域可以访问其父作用域变量这一特点,来提升内层作用域的变量至外层。这样便达到了提升变量作用域的效果。

注意

1
2
3
4
5
6
7
8
9
var fn = function () {
var b = 2;
return function () {
console.log('b:', this.b);
};
};
var getB = fn();
getB();
// b: undefined

这个例子可能并非如你所想可以得到b的值2,它输出了undefind。原因时fn所返回的是一个全新的function,已脱离了fn的上下文环境,this实际指向了全局对象window。

如此修改是否可以得到想要的答案:

1
2
3
4
5
6
7
8
9
10
var fn = function () {
var b = 2;
function getB () {
console.log('b:', this.b);
}.bind(this);
return getB;
};
var getB = fn();
getB();
// b: undefined

以上例子也未能得到预想的答案,原因是函数调用的上下文执行环境是全局对象window(非严格模式),所以并不能得到b的值2。

再次改造:

1
2
3
4
5
6
7
8
9
10
11
var obj = {
b : 2,
getB: function () {
return function () {
return this.b
}
}
};
var res = obj.getB();
console.log(res.call(obj));
// 2

让变量值保持在内存中

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
function fn () {
var a = 1;
add = function () {
a += 1;
};
function f1 () {
console.log('a:', a);
}
return f1;
};

var res = fn();
res(); // a: 1
add();
res(); // a: 2

在上面例子中,将闭包函数f1赋给res变量,两次运行函数res,都可以得到函数fn中的局部变量a,说明变量a并没有在fn调用后从内存中被自动清除。
由于fn是f1的父函数,f1的执行依赖于fn,当闭包函数f1被赋值给变量res时,fn也始终存在于内存中,不会在调用后被回收。

使用闭包的注意点

  • 闭包会使得函数中的变量都被保存在内存中,滥用闭包很容易造成内存泄漏。因此,在退出函数之前,将不适用的局部变量全部删除。
  • 闭包的使用可能会在父函数外部暴露操作父函数内部变量的接口,从而导致内部变量的值更新。在不必要的情况下,尽量只对变量做读操作。