喂,别忙着过七夕了,闭包彻底搞懂了吗?

简介: 喂,别忙着过七夕了,闭包彻底搞懂了吗?

思维导图

微信截图_20221112151213.png


闭包无处不在

闭包是基于词法作用域书写代码时所产生的自然结果,比如在函数中嵌套了函数。

闭包 的特点就是哪里都可以调用它。管tm天上地下。

当函数可以记住并访问所在的词法作用域时,就产生了闭包,即使函数是在当前词法作用域之外执行


闭包的定义

函数嵌套

这次守点规矩,在声明的词法作用域里面调用闭包。

function foo() {
    var a = 2;
    function bar() {
        console.log(a); // 2 RHS查找
    }
    bar();
}
foo();
复制代码


不知道RHS查找的看看这篇哦。

从编译的角度来学作用域!

bar()函数现在自身作用域查找变量a,没得那就去外侧foo函数的作用域找。找到了,拿来就白嫖。不说什么闭包概念我们也好像可以理解这种就是词法作用域的嵌套嘛。答对了这就是闭包的子集(在声明词法作用域内调用)。


孩子大了,要乱跑。去外面的时世界看看

这次讲bar函数拿到foo函数作用域外面的全局作用域去调用。

function foo() {
    var a = 2;
    function bar() {
        console.log(a);
    }
    return bar;
}
var baz = foo();
baz(); // 2 —— 朋友,这就是闭包的效果。
复制代码


这个函数在定义时的词法作用域以外的地方被调用。但是还是“白嫖”到了变量a,闭包使得函数可以继续访问定义时的词法作用域。


做个比喻

闭包就是你,你在家(自身词法作用域)拿钱买东西(调用查找变量),你爸妈敢不给?你要和女朋友去外面过七夕了(其他作用域),要钱你爸妈敢不给?给,给,给啊。


闭包的使用

加个定时器

function wait(message) {
    setTimeout(function timer() {
        console.log(message);
    }, 1000);
}
wait("Hello, closure!")
复制代码


wait函数 执行 1000 毫秒后,它的内部作用域并不会消失,timer函数依然保有wait函数作用域的闭包。


深入到引擎的内部原理中,内置的工具函数 setTimeout(..) 持有对一个参数的引用,这个 参数也许叫作 fn 或者 func,或者其他类似的名字。引擎会调用这个函数,在例子中就是 内部的 timer 函数,而词法作用域在这个过程中保持完整。(白嫖书上的)。


闭包使用场景

如果将函数(访问它们各自的词法作用域)当作第一级的值类型并到处传递,你就会看到闭包在这些函数中的应用。在定时器、事件监听器、 Ajax 请求、跨窗口通信、Web Workers 或者任何其他的异步(或者同步)任务中,只要使用了回调函数,实际上就是在使用闭包。

var a = 2;
(function IIFE() {
    console.log(a);
})();
复制代码


因为函数(示例代码中 的 IIFE)并不是在它本身的词法作用域以外执行的。它在定义时所在的作用域中执行(而 外部作用域,也就是全局作用域也持有 a)。a 是通过普通的词法作用域查找而非闭包被发现的。


IIFE(立即调用函数)

立即调用函数会创建一个包裹自己的作用域。

var a = 2;
(function IIFE() {
    console.log(a); // 2 RHS查找
})();
复制代码

立即调用函数是一个“宅男”,永远在自己的作用域里面调用。它创建了闭包,是最常用来创建可以被封闭起来的闭包的工具。


循环和闭包

共享作用域

for (var i = 0; i < 10; i++) {
    setTimeout(function () {
        console.log(i) // 全是10
    }, 0)
}
复制代码

这是为什么,每次循环我们都声明了一个匿名函数到事件队列,但是这些声明的函数都是共享的一个作用域,等到for循环执行完毕,i = 10,那么这些函数就该执行了,自然都会去找这个i = 10的麻烦。


IIFE破冰

套了个壳

for (var i = 1; i <= 5; i++) {
    (function () {
        setTimeout(function timer() {
            console.log(i); // 全是6
        }, i * 1000);
    })();
}
复制代码

每次创建匿名函数都会创建一个作用域,可惜还是6。为什么,生儿子再多,都是败家子,还不得啃老,来白嫖变量i。原来和上面一样了。创建的作用域也不就是多个“壳子”。


传递参数

核心 将每次循环的变量i即时保存进自己的作用域,每次循环每个作用域保存的变量i值就不一样了。

下面这2种都行。

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


使用let

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

for 循环头部的 let 声明还会有一 个特殊的行为。这个行为指出变量在循环过程中不止被声明一次,每次迭代都会声明。随 后的每个迭代都会使用上一个迭代结束时的值来初始化这个变量。


xdm,模块那就先鸽了,我要去过七夕了,哦,忘了,我还是一个人~🤷‍♀️🤷‍♀️



目录
相关文章
|
3月前
|
存储 自然语言处理 JavaScript
var为什么会变量提升?一盏茶的功夫让你彻底熟悉预编译 ——小白请看
var为什么会变量提升?一盏茶的功夫让你彻底熟悉预编译 ——小白请看
|
6月前
|
存储 编译器 C#
救命!揭秘C关键字,小白也能变大神
救命!揭秘C关键字,小白也能变大神
28 3
|
自然语言处理 JavaScript
再谈JS闭包
作用域 作用域嵌套 词法作用域(lexicsl scope) 闭包 闭包示例
207 0
|
前端开发 C语言
带你读书之“红宝书”:第十章 函数⑨
带你读书之“红宝书”:第十章 函数⑨
95 0
带你读书之“红宝书”:第十章 函数⑨
|
前端开发 JavaScript C语言
带你读书之“红宝书”:第十章 函数⑧
带你读书之“红宝书”:第十章 函数⑧
81 0
带你读书之“红宝书”:第十章 函数⑧
|
JavaScript 前端开发
学弟的一张图,让我重学了一遍函数声明和函数表达式!
首先我们要知道,当函数声明与变量命名冲突的时候,要保持着**函数声明优先的原则**
|
前端开发 JavaScript
悟透前端:加深Javascript变量函数声明提升理解
Javascript变量函数声明提升(Hoisting)是在 Javascript 中执行上下文工作方式的一种认识(也可以说是一种预编译),从字面意义上看,“变量提升”意味着变量和函数的声明会在物理层面移动到代码的最前面,在代码里的位置是不会动的,而是在编译阶段被放入内存中会和代码顺序不一样。
98 0
悟透前端:加深Javascript变量函数声明提升理解
|
存储 前端开发 对象存储
前端百题斩【011】——通俗易懂的变量对象
前端百题斩【011】——通俗易懂的变量对象
前端百题斩【011】——通俗易懂的变量对象
|
自然语言处理 前端开发 JavaScript
前端百题斩【013】——用“闭包”问题征服面试官
前端百题斩【013】——用“闭包”问题征服面试官
前端百题斩【013】——用“闭包”问题征服面试官
|
存储 自然语言处理 前端开发
前端百题斩【010】——通俗易懂的JavaScript执行上下文
前端百题斩【010】——通俗易懂的JavaScript执行上下文
前端百题斩【010】——通俗易懂的JavaScript执行上下文