函数 和 函数内部能访问到的变量(也叫环境)的总和,就是一个闭包。也就是说,闭包让你可以在一个内层函数中访问到其外层函数的作用域。
我们知道了闭包的定义,那么就从定义下手,开始学习什么是闭包。
作用域
每个函数都会默认创造一个作用域。作用域指的是变量存在的范围。
在 ES5 的规范中,JavaScript 只有两种作用域:
一种是全局作用域,变量在整个程序中一直存在,所有地方都可以读取,这种变量称为“全局变量”;
var a = 1;
function f() {
console.log(a);
}
f()
另一种是函数作用域,变量只在函数内部存在,外部无法读取,这种变量称为“局部变量”。
function f() {
var a = 1;
}
console.log(a);
f()
闭包
通过对比上面两段代码,我们可以看出内部的函数可以访问到外部函数的变量,我们再举一个例子。
function f1() {
var name = "名字"; // name 是一个被 f1 创建的局部变量
function f2() { // f2() 是内部函数,一个闭包
alert(name); // 使用了父函数中声明的变量
}
f2();
}
f1();
JavaScript中的函数会形成了闭包。
闭包是由函数以及声明该函数的词法环境组合而成的。该环境包含了这个闭包创建时作用域内的任何局部变量。
在上面的代码里,
f1()
创建了一个局部变量name
和一个名为f2()
的函数。f2()
是定义在f1()
里的内部函数,并且仅在f1()
函数体内可用。f2()
没有自己的局部变量,但因为f2()可以访问到外部函数的变量,所以f2()
可以使用父函数f1()
中声明的变量name
。f2
的实例维持了一个对它的词法环境(变量name
存在于其中)的引用。- 这里的
f2()
就是个闭包
既然f2可以读取f1中的局部变量,那么只要把f2作为返回值,就可以在f1外部读取它的内部变量了。
function f1() {
var name = "名字";
function f2() {
alert(name);
}
return f2;
}
var f = f1();
f();
作用
闭包可以读取函数内部的变量,换句话说,可以隐藏一个变量。
function create_counter(initial) {
var x = initial || 0;
return {
inc: function () {
x += 1;
return x;
}
}
}
它用起来像这样:
var c1 = create_counter();
c1.inc(); // 1
c1.inc(); // 2
c1.inc(); // 3
var c2 = create_counter(10);
c2.inc(); // 11
c2.inc(); // 12
c2.inc(); // 13
在返回的对象中,实现了一个闭包,该闭包携带了局部变量x
,并且,从外部代码根本无法访问到变量x
。换句话说,闭包就是携带状态的函数,并且它的状态可以完全对外隐藏起来。
闭包还可以把多参数的函数变成单参数的函数。
例如,要计算x可以用Math.pow(x, y)
函数,不过考虑到经常计算x或x,可以利用闭包创建新的函数pow2
和pow3
:
function make_pow(n) {
return function (x) {
return Math.pow(x, n);
}
}
// 创建两个新函数:
var pow2 = make_pow(2);
var pow3 = make_pow(3);
console.log(pow2(5)); // 25
console.log(pow3(7)); // 343
缺点
- 闭包在处理速度和内存消耗方面对脚本性能具有负面影响。例如,在创建新的对象或者类时,方法通常应该关联于对象的原型,而不是定义到对象的构造器中。原因是这将导致每次构造器被调用时,方法都会被重新赋值一次(也就是说,对于每个对象的创建,方法都会被重新赋值)。
- 闭包会在父函数外部,改变父函数内部变量的值。所以,如果你把父函数当作对象(object)使用,把闭包当作它的公用方法(Public Method),把内部变量当作它的私有属性(private value),这时一定要小心,不要随便改变父函数内部变量的值。