关于闭包,初学者会被绕的晕头转向,在学习的路上也付出了很多精力来理解。 让我们一起来揭开闭包神秘的面纱。

闭包晦涩的定义

看过很多关于闭包的定义,很多讲的云里雾里,晦涩难懂。让不少人以为闭包是多么玄乎的东西。在我看过的所有书籍中,我更喜欢《你不知道的javascript(上卷)》的定义:

当函数可以记住并访问所在的词法作用域时,就产生了闭包,或者说函数在创建时的词法作域之外执行。

通俗的来说(不严谨): 就是函数套函数,子函数可以有权访问父函数的变量、父函数的父函数的变量、一直到全局变量。子函数如果不被销毁,整条作用域链上的变量仍然保存在内存中。

关于词法作用域,这里不做过多的解释,详情参考:http://www.cnblogs.com/ylweb/p/7531259.html.

下面用一些代码来解释这个定义:

function foo() {
var a = 2; function bar() {
console.log( a ); // 2
}
bar();
} foo();

严格来说这段代码并没有形成闭包,因为bar是在创建时所在的词法作用域执行。bar() 对a 的引用的方法是词法作用域的查找规则,而这些规则只是闭包的一部分。从学术的角度说:,在上面的代码片段中,函数bar() 具有一个涵盖foo() 作用域的闭包

(事实上,涵盖了它能访问的所有作用域,比如全局作用域)。也可以认为bar() 被封闭在

了foo() 的作用域中。为什么呢?原因简单明了,因为bar() 嵌套在foo() 内部。

下面我们来看一段代码,清晰地展示了闭包:

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

函数bar() 的词法作用域能够访问foo() 的内部作用域。然后我们将bar() 函数本身当作一个值类型进行传递。在这个例子中,我们将bar 所引用的函数对象本身当作返回值。

在foo() 执行后,其返回值(也就是内部的bar() 函数)赋值给变量baz 并调用baz(),实际上只是通过不同的标识符引用调用了内部的函数bar()。

bar() 显然可以被正常执行。但是在这个例子中,它在自己定义的词法作用域以外的地方执行。

在foo() 执行后,通常会期待foo() 的整个内部作用域都被销毁,因为我们知道引擎有垃圾回收器用来释放不再使用的内存空间。由于看上去foo() 的内容不会再被使用,所以很自然地会考虑对其进行回收。

而闭包的“神奇”之处正是可以阻止这件事情的发生。事实上内部作用域依然存在,因此没有被回收。谁在使用这个内部作用域?原来是bar() 本身在使用。

拜bar() 所声明的位置所赐,它拥有涵盖foo() 内部作用域的闭包,使得该作用域能够一直存活,以供bar() 在之后任何时间进行引用。

bar() 依然持有对该作用域的引用,而这个引用就叫作闭包。

循环与闭包

以下是一个最常见的for循环例子:

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

正常情况下,我们对这段代码行为的预期是分别输出数字1~5,但实际上,这段代码在运行时输出五次6。 Why?

首先解释6 是从哪里来的。这个循环的终止条件是i 不再<=5。条件首次成立时i 的值是6。因此,输出显示的是循环结束时i 的最终值。

仔细想一下,这好像又是显而易见的,延迟函数的回调会在循环结束时才执行。事实上,当定时器运行时即使每个迭代中执行的是setTimeout(.., 0),所有的回调函数依然是在循环结束后才会被执行,因此会每次输出一个6 出来。

问题实质:

我们试图假设循环中的每个迭代在运行时都会给自己“捕获”一个i 的副本。但是根据作用域的工作原理,实际情况是尽管循环中的五个函数是在各个迭代中分别定义的,但是它们都被封闭在一个共享的全局作用域中,因此实际上只有一个i

改进方案:

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

ES6改进:

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

JS闭包—你不知道的JavaScript上卷读书笔记(二)的更多相关文章

  1. JavaScript词法作用域—你不知道的JavaScript上卷读书笔记(一)

    前段时间在每天往返的地铁上抽空将 <你不知道的JavaScript(上卷)>读了一遍,这本书很多部分写的很是精妙,对于接触前端时间不太久的人来说,就好像是叩开了JavaScript的另一扇 ...

  2. JavaScript中的对象与原型—你不知道的JavaScript上卷读书笔记(四)

    一.对象 对象可以通过两种形式定义:声明(文字)形式和构造形式.即: var myObj = { key: value // ... }; 或: var myObj = new Object(); m ...

  3. 你不知道的javascript 上卷 读书笔记

    <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8&quo ...

  4. JavaScript中的this—你不知道的JavaScript上卷读书笔记(三)

    this是什么? this 是在运行时进行绑定的,并不是在编写时绑定,它的上下文取决于函数调用时的各种条件.this 的绑定和函数声明的位置没有任何关系,只取决于函数的调用方式.当一个函数被调用时,会 ...

  5. 《你不知道的javascript》读书笔记2

    概述 放假读完了<你不知道的javascript>上篇,学到了很多东西,记录下来,供以后开发时参考,相信对其他人也有用. 这篇笔记是这本书的下半部分,上半部分请见<你不知道的java ...

  6. 《你不知道的javascript》读书笔记1

    概述 放假读完了<你不知道的javascript>上篇,学到了很多东西,记录下来,供以后开发时参考,相信对其他人也有用. js的工作原理 引擎:从头到尾负责整个js的编译和运行.(很大一部 ...

  7. 《你不知道的JavaScript》读书笔记(二)词法作用域

    JavaScript 采用的是 词法作用域 的工作模型. 定义 词法化:大部分标准语言编译器的第一个工作阶段叫词法化(单词化),这个过程会对源代码中的字符进行检查,如果是有状态的解析过程,还会赋予单词 ...

  8. 《你不知道的JavaScript -- 上卷》笔记 --- 基于ES6新标准

    1.let A:let关键字:将变量绑定到所在的任意作用域 function process(){ //do something } //在这个块中定义的内容完事就可以销毁 { let someRea ...

  9. 《你不知道的JavaScript》读书笔记(一)作用域

    名词 引擎:从头到尾负责整个 JavaScript 程序的 编译 及 执行 过程. 编译器:负责 语法分析 及 代码生成. 作用域:负责收集并维护由所有声明的标识符(变量)组成的一系列查询,并实施一套 ...

随机推荐

  1. Paper | Beyond a Gaussian Denoiser: Residual Learning of Deep CNN for Image Denoising

    目录 故事背景 网络结构 BN和残差学习 拓展到其他任务 发表在2017 TIP. 摘要 Discriminative model learning for image denoising has b ...

  2. JavaScript查找两个数组的相同元素和相差元素

    let intersection = a.filter(v => b.includes(v)) 返回交集数组 let difference = a.concat(b).filter(v => ...

  3. react、less、antd-mobile 报错Inline JavaScript is not enabled. Is it set in your options?

    增加less-loader里面的配置.如图或者降级less到2.x版本

  4. 2019.11.21 做OJ题的反思

    1.利用二分法查找数组元素(适用于有序数组) #include<stdio.h> int BinarySearch(int a[],int n,int key); ]; int main( ...

  5. 奥展项目笔记05--域名、端口、Nginx相关知识笔记

    1.我国的电信运营商是默认封闭80端口的. 中国电信屏蔽ADSL用户80端口 只是做应用服务器的话你可以使用别的端口. 80端口电脑上同时有各种各样的程序在运行,他们都需要借助网络来进行通信.例如,你 ...

  6. 第一届云原生应用大赛火热报名中! helm install “一键安装”应用触手可及!

    云原生应用,是指符合“云原生”理念的应用开发与交付模式,这是当前在云时代最受欢迎的应用开发最佳实践. 在现今的云原生生态当中,已经有很多成熟的开源软件被制作成了 Helm Charts,使得用户可以非 ...

  7. Pandas 学习 第9篇:DataFrame - 数据的输入输出

    常用的数据存储介质是数据库和csv文件,pandas模块包含了相应的API对数据进行输入和输出: 对于格式化的平面文件:read_table() 对于csv文件:read_csv().to_csv() ...

  8. LeetCode 1291. Sequential Digits

    题目 class Solution { public: int ans[10005]; vector<int> sequentialDigits(int low, int high) { ...

  9. kali渗透综合靶机(十四)--g0rmint靶机

    kali渗透综合靶机(十四)--g0rmint靶机 靶机下载地址:https://www.vulnhub.com/entry/g0rmint-1,214/ 一.主机发现 1.netdiscover - ...

  10. TCP SYN flood洪水攻击原理和防御破解

    简介 TCP协议要经过三次握手才能建立连接: 于是出现了对于握手过程进行的攻击.攻击者发送大量的SYN包,服务器回应(SYN+ACK)包,但是攻击者不回应ACK包,这样的话,服务器不知道(SYN+AC ...