跳到主要内容

闭包

涉及面试题:什么是闭包?

闭包的定义其实很简单:函数 A 内部有一个函数 B,函数 B 可以访问到函数 A 中的变量,那么函数 B 就是闭包。

function A() {
let a = 1
window.B = function () {
console.log(a)
}
}
A()
B() // 1

很多人对于闭包的解释可能是函数嵌套了函数,然后返回一个函数。其实这个解释是不完整的,就比如我上面这个例子就可以反驳这个观点。 在 JS 中,闭包存在的意义就是让我们可以间接访问函数内部的变量。 经典面试题,循环中使用闭包解决 var 定义函数的问题

for (var i = 1; i <= 5; i++) {
setTimeout(function timer() {
console.log(i)
}, i * 1000)
}

首先因为 setTimeout 是个异步函数,所以会先把循环全部执行完毕,这时候 i 就是 6 了,所以会输出一堆 6。 解决办法有三种,第一种是使用闭包的方式

for (var i = 1; i <= 5; i++) {
;(function(j) {
setTimeout(function timer() {
console.log(j)
}, j * 1000)
})(i)
}

在上述代码中,我们首先使用了立即执行函数将 i 传入函数内部,这个时候值就被固定在了参数 j 上面不会改变,当下次执行 timer 这个闭包的时候,就可以使用外部函数的变量 j,从而达到目的。 第二种就是使用 setTimeout 的第三个参数,这个参数会被当成 timer 函数的参数传入。

for (var i = 1; i <= 5; i++) {
setTimeout(
function timer(j) {
console.log(j)
},
i * 1000,
i
)
}

第三种就是使用 let 定义 i 了来解决问题了,这个也是最为推荐的方式

for (let i = 1; i <= 5; i++) {
setTimeout(function timer() {
console.log(i)
}, i * 1000)
}

1.闭包:闭包指的是一个函数可以访问它的词法作用域,即使这个函数在它的词法作用域外执行

closure 返回一个函数就形成了闭包,返回的函数能够持续访问它的词法作用域,即使在它的词法作用域外执行

function closure () {
var name = 'zhoushaw';
return function () {
console.log(name);
}
}
var fn = closure();
fn();

相关问题#

● 什么是闭包 ● 闭包的应用 回答关键点# 作用域 引用 函数 作用:能够在函数定义的作用域外,使用函数定义作用域内的局部变量,并且不会污染全局。 原理:基于词法作用域链和垃圾回收机制,通过维持函数作用域的引用,让函数作用域可以在当前作用域外被访问到。

知识点深入#

1. 作用域#

● 作用域:用于确定在何处以及如何查找变量(标识符)的一套规则。 ● 词法作用域:词法作用域是定义在词法阶段的作用域。词法作用域是由写代码时将代码和块作用域写在哪里来决定的,因此当词法作用域处理代码是会保持作用域不变(大部分情况)。 ● 块作用域:指的是变量和函数不仅可以属于所处的作用域,也可以属于某个代码块(通常用{}包裹)。常见的块级作用域有 with,try/catch,let,const 等。 ● 函数作用域:属于这个函数的全部变量都可以在整个函数范围内使用及复用(包括嵌套作用域)。 ● 作用域链:查找变量时,先从当前作用域开始查找,如果没有找到,就会到父级(词法层面上的父级)作用域中查找,一直找到全局作用域。作用域链正是包含这些作用域的列表。

2. 什么是闭包#

当函数可以记住并访问所在的词法作用域时,就产生了闭包,即使是函数在当前词法作用域外执行。 ——《你不知道的 JavaScript》 function foo() { var a = "hzfe"; function bar() { console.log(a); } return bar; } var baz = foo();baz(); // hzfe

在这个例子中,函数 bar 作为返回值返回后,在自己定义的词法作用域以外的地方执行。一般来说,在函数 foo 执行后,通常会期待函数 foo 的整个内部作用域被引擎回收机制销毁。而闭包可以阻止这件事情的发生。事实上内部作用域依然存在,因为函数 bar 本身在使用,所以并不会被回收。 在 JavaScript 中,每当创建一个函数,闭包就会在函数创建的同时被创建出来。

3. 闭包的应用#

无论何时何地,如果将函数作为返回值,就会看到闭包在这些函数中的应用。在定时器,事件监听器,ajax 请求,跨窗口通信,web workers 或者任何其他的异步/同步任务中,只要使用了回调函数,实际上就是使用闭包。使用闭包的例子可以参考实现节流防抖函数。 TIPS: 闭包与执行函数关系 var a = "hzfe";(function IIFE() { console.log(a);})(); Copy 通常认为立即执行函数(IIFE)是典型的观察闭包的典型例子,但严格来说并不是。虽然创建了闭包,但没有体现出闭包的作用。因为函数并不是在它本身的词法作用域以外执行的。 它在定义时所在的作用域中执行,而非外部作用域。

参考

  1. 闭包 MDN
  2. 垃圾回收机制
  3. 你不知道的 JavaScript(上卷)

For more details on closures, see these two websites. 详细了解闭包可以看这两个网站。 https://zh.wikipedia.org/wiki/闭包_(计算机科学) https://academic.oup.com/comjnl/article/6/4/308/375725