本文将带你用正确姿势看待JavaScript闭包。
在 JavaScript 中闭包描述的是 function 中 外层作用域的变量 被内层作用域 引用的场景,闭包的结构为 内层作用域 保存了 外层作用域的变量。
要理解闭包,首先要知道 JS词法作用域 是如何工作的。
JS词法作用域(lexical scoping)
来看这段代码:
let name = 'John';
function greeting() {
let message = 'Hi';
console.log(message + ' '+ name);
}
变量 name 是全局变量。它可以在任何地方调用,包括在 greeting 函数内部。
变量 message 是局部变量,只能在 greeting 函数内部调用。
如果你尝试从 greeting() 外部访问 message 变量,会抛出一个错误:
ReferenceError: message is not defined
比较有意思的是 函数内部的作用域是可以嵌套的,如下:
function greeting() {
let message = 'Hi';
function sayHi() {
console.log(message);
}
sayHi();
}
greeting();
// Hi
greeting() 函数 创建了一个局部变量 message 和一个局部函数 sayHi()。
sayHi() 是 greeting() 的一个内部方法,只能在 greeting() 内部访问。sayHi() 可以访问 greeting() 的 message 变量。在 greeting() 内部调用了 sayHi(),打印出了变量 message 的值。
JavaScript闭包(closures)
来修改一下greeting:
function greeting() {
let message = 'Hi';
function sayHi() {
console.log(message);
}
return sayHi;
}
let hi = greeting();
hi(); // 仍然可以获取到message的值
这次我们不是在 greeting() 执行 sayHi(),而是在 greeting() 被调用时把 sayHi 作为结果返回。
在 greeting() 函数外部,声明了一个变量 hi,它是 sayHi() 函数的索引。
这时,我们通过这个索引来执行 sayHi() 函数,可以得到和之前一样的结果。
通常情况下,一个局部变量只会在函数执行的时候存在,函数执行完成,会被垃圾回收机制回收。
有意思的是,上边的这种写法当我们执行 hi(),message 变量是会一直存在的。这就是闭包的作用,换句话说上面的这种形式就是闭包。
其他示例
下面的例子阐述了闭包更加实用的情况:
function greeting(message) {
return function(name){
return message + ' ' + name;
}
}
let sayHi = greeting('Hi');
let sayHello = greeting('Hello');
console.log(sayHi('John')); // Hi John
console.log(sayHello('John')); // Hello John
greeting() 接收一个参数(message),返回了一个函数接收 一个参数(name)。
greeting 返回的匿名函数 把 message 和 name 做了拼接。
这时 greeting() 表现的行为像 工厂模式。使用它创建了 sayHi() 和 sayHello() 函数,它们都维护了各自的 message ”Hi“ 和 ”Hello“。
sayHi() 和 sayHello() 都是闭包。它们共用了同一个函数体,但是保存了不同的作用域。
防抖和节流
在面试的时候,经常会有面试官让你手写一个防抖,节流函数,其实用到的就是闭包。
如果有兴趣可以 查看一下这篇文章 《防抖和节流实例讲解》
好处和问题
闭包的优势
闭包可以在自己的作用域保存变量的状态,不会污染全局变量。因为如果有很多开发者开发同一个项目,可能会导致全局变量的冲突。
闭包可能导致的问题
闭包的优势可能会成为严重的问题,因为闭包中的变量无法被GC回收,尤其是在循环中使用闭包:
function outer() {
const potentiallyHugeArray = [];
return function inner() {
potentiallyHugeArray.push('Hello'); // function inner is closed over the potentiallyHugeArray variable
console.log('Hello');
};
};
const sayHello = outer(); // contains definition of the function inner
function repeat(fn, num) {
for (let i = 0; i < num; i++){
fn();
}
}
repeat(sayHello, 10); // each sayHello call pushes another 'Hello' to the potentiallyHugeArray
// now imagine repeat(sayHello, 100000)
在这个例子中,potentiallyHugeArray 会随着循环的次数增加而无限增大而导致内存泄漏(Memory Leaks)。
总结
闭包既有优势,也会导致问题。只有理解了它的原理,才能让它发挥正确的作用。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。