以下为本人学习所记,若有不对,望指出。
1. 变量的作用域
根据作用域的不同,JavaScript 中的变量可分为两种:全局变量和局部变量。
其中,函数内部可以直接读取全局变量:
var a = 1;
function fn1() {
console.log(a);
}
fn1(); // 1
但是,函数外部不能读取函数内的局部变量:
function fn2() {
var b = 2;
}
console.log(b); // ReferenceError: b is not defined
定义局部变量时要注意,要使用关键字
var
、const
或let
声明,若像下面这样声明,相当于window.b = 2
,声明了一个全局变量!
function fn2() {
b = 2; // 不要这样声明,这样相当于声明了全局变量
}
fn2();
console.log(b); // 2
2. 什么是闭包
以下摘自MDN:
一个函数和对其周围状态(lexical environment,词法环境)的引用捆绑在一起(或者说函数被引用包围),这样的组合就是闭包(closure)。也就是说,闭包让你可以在一个内层函数中访问到其外层函数的作用域。在 JavaScript 中,每当创建一个函数,闭包就会在函数创建的同时被创建出来。
我们看下面这个例子,在函数内部,再定义一个函数,并将其作为返回值,返回一个函数:
function fn1() {
let a = 1;
return function fn2() {
console.log(a);
};
}
// 因为fn1函数有返回值,用result接收
const result = fn1();
result(); // 1
JavaScript中的函数会形成闭包。 闭包是由函数以及声明该函数的词法环境组合而成的。该环境包含了这个闭包创建时作用域内的任何局部变量。在本例中,result
是执行fn1
时创建的fn2
函数实例的引用。fn2
的实例维持了一个对它的词法环境(变量a
存在于其中)的引用。因此,当result
被调用时,变量a
仍然可用,其值就被传递到console.log
中。
下面看一个makeAdder
函数:
function makeAdder(x) {
return function (y) {
return x + y;
};
}
const add5 = makeAdder(5);
const add10 = makeAdder(10);
console.log(add5(2)); // 7
console.log(add10(2)); // 12
add5
和add10
都是闭包。它们共享相同的函数定义,但是保存了不同的词法环境。在add5
的环境中,x
为 5。而在add10
中,x
则为 10。
3. 闭包的作用
1. 在函数外部访问到函数内部变量
从上述例可看出,闭包可以在函数外部访问到函数内部的变量,这是其第一个作用。
2. 使变量留存在内存中
闭包另一个作用,使已经运行结束的函数上下文中的变量对象继续留在内存中。看下面一段 demo:
function fn1() {
var a = 1;
return function fn2() {
console.log(a++);
};
}
var result = fn1();
result(); // 1
result(); // 2
result(); // 3
a
是函数fn1
中的局部变量,a
的值在函数fn2
中改变,fn2
每执行一次,a
就+1
。
而上述代码执行 3 次result()
后,分别输出了a
的值,a
不断地+1
。这说明函数fn1
中的变量a
一直保存在内存中,并没有在函数fn1
调用后被清除。
因为函数fn2
被赋给了一个全局变量,因此fn2
会一直在内存中,而fn2
的存在依赖于fn1
,所以fn1
也一直保存在内存中,并不会调用后被垃圾回收机制清除。
3. 模拟私有方法
JavaScript不支持将方法声明为私有,但可以用闭包来模拟私有方法。下面的示例展现了如何使用闭包来定义公共函数,并令其可以访问私有函数和变量。
const counter = function () {
let privateCounter = 0;
return {
add() {
privateCounter++;
},
min() {
privateCounter--;
},
value() {
return privateCounter;
},
};
};
const counter1 = counter();
const counter2 = counter();
counter1.add();
counter1.add();
counter1.add();
counter2.min();
console.log(counter1.value()); // 3
console.log(counter2.value()); // -1
两个计数器counter1
和counter1
都保持各自的独立性。每个闭包都是引用自己作用域内的变量privateCounter
,每次调用其中一个计数器时,通过改变这个变量的值,会改变这个闭包的词法环境。另外,在一个闭包内对变量的修改,不会影响到另外一个闭包中的变量。
4. 闭包的副作用
1. 常驻内存
闭包会使得函数中的变量都被保存在内存中,内存消耗很大,不能滥用闭包,否则会造成严重的性能问题,在IE浏览器中可能由于循环引用而导致内存泄露。
解决方法:在退出函数之前,将不使用的局部变量全部清除。
2. 误改函数内部的值
不要随便改变父函数内部变量的值。
参考文章: