前言
面试官:什么是闭包?
我:闭包就是函数执行的时候返回一个函数并且引用函数内部的变量,使得函数在执行完成以后不会被垃圾回收机制回收。
面试官:那一定要返回一个函数吗? 返回一个对象行不行呢?
我: 应该行吧。
面试官: 那么在开发过程中常见的闭包有什么呢?
我:节流函数? 防抖函数?
面试官: 还有呢?
我: 不知道了。
大家觉得答的怎么样?很明显这是一次非常失败的面试。那么为什么会这样呢?在面试前被被八股文,看到里面对闭包生涩的解释,非常难理解,所以一旦面试官展开问一下,那么你一定会被虐的体无完肤。好了,为了我,和大家能以后和面试官好好battle几轮,这里我们一起来吃透闭包的,理解闭包。
1. 闭包的定义
首先我们来看一下mdn上对闭包的定义: 闭包(closure)是一个函数以及其捆绑的周边环境状态(lexical environment,词法环境)的引用的组合。换而言之,闭包让开发者可以从内部函数访问外部函数的作用域。在 JavaScript 中,闭包会随着函数的创建而被同时创建。
看到这段我就一脸懵逼了,算了!背八股文吧!
本着上面的态度,在面试过程中发现面试官及其残忍,没有意外,我成功的被按在地上摩擦。
那我先用我自己能看懂的话(我都能看懂大家应该都能看懂)来解释一下什么是闭包:
在一个函数内还有一个函数,这个
内部函数中用到了外部函数的变量,并且这个内部函数是在当前作用域以外的随便什么地方被调用,这样就形成了闭包。
好了,记住上面我给的解释继续往下看。
2. 认识闭包
根据上面我给的解释,达成闭包其实可以分为三个条件,如果都达成就可以称之为闭包。
- 一个
外部函数内定义了一个内部函数 内部函数引用了外部函数的变量内部函数在当前作用域以外的地方被调用
1. 有了上面给的条件,我们接下来看几个例子,判断一下能不能称为闭包:
function foo() {
var a = 2;
function bar() { // 内部函数
console.log( a ); // 2
}
bar();
}
foo();
这段代码看上去就是一个闭包,首先满足了第一点,有一个内部函数,并且满足第二点内部函数引用了外部函数。那么第三点呢?很明显不满足,因此上面代码不能称为闭包.
2. 在下面这段代码则很清晰了展示了一个闭包。
function foo() {
var a = 2;
function bar() {
console.log( a ); }
return bar;
}
var baz = foo();
baz(); // 2
上面这个例子才是闭包的效果。我们可以一起来过一下,很明显满足一二两个条件那么第三点呢?通过将内部函数return出来使得内部函数在当前作用域以外调用。perfect!这就是一个完美的闭包。
3. 那么为了满足第三个条件,我们只能通过将内部函数来return出来才能达成吗?
当然不是,达成第三点的方法还有很多,我们可以继续往下看。
function foo() {
var a = 2;
function baz() {
console.log( a ); // 2
}
bar( baz );
}
function bar(fn) {
fn(); // 看这里!这也是闭包啊!
}
这里其实是通过作用域链全局作用域下的bar函数然后通过回调的形式在bar的作用域中调用内部函数,因此这也是一个闭包。
var fn;
function foo() {
var a = 2;
function baz() {
console.log( a );
}
fn = baz; // 将 baz 分配给全局变量
}
function bar() {
fn(); // 妈妈快看呀,这就是闭包!
}
foo();
bar(); // 2
我们可以看到,通过达到定义中第三点形成闭包的方法有很多。只要在内部函数的作用域以外调用内部函数,就可以形成闭包.
我们常见或者常用的闭包
现在大家应该都能简单介绍一下闭包了,并且可以准确的看出代码中的闭包了。那么现在又有个问题,你能说出一些闭包在我们日常开发中的应用吗?
这里我给大家列举一些,欢迎大家继续补充。
function wait(message) {
setTimeout(
function timer() {
console.log( message );
}, 1000 );
}
wait( "Hello, closure!" );
这里将一个timer的内部函数通过参数的形式传递给setTimeout,并且引用了外部函数wait中的message变量,setTimeout函数会在1000ms之后在setTimeout作用域下回调timer函数。很明显满足闭包的三点。
function setupBot(name, selector) {
$( selector ).click(
function activator() {
console.log( "Activating: " + name );
}
);
}
如果你熟悉jquery,可以看下上面的例子.
结论
当作第一 级的值类型并到处传递,你就会看到闭包在这些函数中的应用。在定时器、事件监听器、 Ajax 请求、跨窗口通信、Web Workers 或者任何其他的异步(或者同步)任务中,只要使 用了回调函数,实际上就是在使用闭包!
总结:
什么是闭包?
满足下面三个条件就是闭包。
- 一个
外部函数内定义了一个内部函数 内部函数引用了外部函数的变量内部函数在当前作用域以外的地方被调用
闭包的应用有哪些?
使用了回调函数的场景都是闭包。
- 定时器
- 事件监听器
- Ajax,promise等网络请求
- Web Workers