JS闭包—你不知道的JavaScript上卷读书笔记(二)
关于闭包,初学者会被绕的晕头转向,在学习的路上也付出了很多精力来理解。 让我们一起来揭开闭包神秘的面纱。
闭包晦涩的定义
看过很多关于闭包的定义,很多讲的云里雾里,晦涩难懂。让不少人以为闭包是多么玄乎的东西。在我看过的所有书籍中,我更喜欢《你不知道的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上卷读书笔记(二)的更多相关文章
- JavaScript词法作用域—你不知道的JavaScript上卷读书笔记(一)
前段时间在每天往返的地铁上抽空将 <你不知道的JavaScript(上卷)>读了一遍,这本书很多部分写的很是精妙,对于接触前端时间不太久的人来说,就好像是叩开了JavaScript的另一扇 ...
- JavaScript中的对象与原型—你不知道的JavaScript上卷读书笔记(四)
一.对象 对象可以通过两种形式定义:声明(文字)形式和构造形式.即: var myObj = { key: value // ... }; 或: var myObj = new Object(); m ...
- 你不知道的javascript 上卷 读书笔记
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8&quo ...
- JavaScript中的this—你不知道的JavaScript上卷读书笔记(三)
this是什么? this 是在运行时进行绑定的,并不是在编写时绑定,它的上下文取决于函数调用时的各种条件.this 的绑定和函数声明的位置没有任何关系,只取决于函数的调用方式.当一个函数被调用时,会 ...
- 《你不知道的javascript》读书笔记2
概述 放假读完了<你不知道的javascript>上篇,学到了很多东西,记录下来,供以后开发时参考,相信对其他人也有用. 这篇笔记是这本书的下半部分,上半部分请见<你不知道的java ...
- 《你不知道的javascript》读书笔记1
概述 放假读完了<你不知道的javascript>上篇,学到了很多东西,记录下来,供以后开发时参考,相信对其他人也有用. js的工作原理 引擎:从头到尾负责整个js的编译和运行.(很大一部 ...
- 《你不知道的JavaScript》读书笔记(二)词法作用域
JavaScript 采用的是 词法作用域 的工作模型. 定义 词法化:大部分标准语言编译器的第一个工作阶段叫词法化(单词化),这个过程会对源代码中的字符进行检查,如果是有状态的解析过程,还会赋予单词 ...
- 《你不知道的JavaScript -- 上卷》笔记 --- 基于ES6新标准
1.let A:let关键字:将变量绑定到所在的任意作用域 function process(){ //do something } //在这个块中定义的内容完事就可以销毁 { let someRea ...
- 《你不知道的JavaScript》读书笔记(一)作用域
名词 引擎:从头到尾负责整个 JavaScript 程序的 编译 及 执行 过程. 编译器:负责 语法分析 及 代码生成. 作用域:负责收集并维护由所有声明的标识符(变量)组成的一系列查询,并实施一套 ...
随机推荐
- 「ZJOI2019」浙江省选
在八月来临前补完了zjoi2019 本来是想在八月前做完暑假作业的? 传送门 Description 给\(n\)条斜率为正的直线,询问每条直线是否在某处高度为前\(m\)名,如果是,询问最小排名 S ...
- [2019BUAA软工助教]助教学期总结
[2019BUAA软工助教]助教学期总结 一.量化自评 线上 博客点评:https://www.cnblogs.com/ChildishChange/MyComments.html 共 106 条 博 ...
- Debug 路漫漫-12:Python: ValueError: 'userid' is both an index level and a column label, which is ambiguous.
啊,又遇到难题了 == 想要对两个 dataframe 做自然连接 merge,连接的key 为 “userid”,但是报错:ValueError: 'userid' is both an index ...
- Logstash:Data转换,分析,提取,丰富及核心操作
Logstash:Data转换,分析,提取,丰富及核心操作 Logstash plugins Logstash是一个非常容易进行扩张的框架.它可以对各种的数据进行分析处理.这依赖于目前提供的超过200 ...
- C 结构体、位域
参考链接:https://www.runoob.com/cprogramming/c-structures.html 结构体是干啥的 例如数组可以用来存储多个相同数据类型的数据项,结构体也是一种数据类 ...
- MySQL基础(二)(约束以及修改数据表)
一,约束以及修改数据表 约束的作用?1.约束保证数据的完整性.一致性:2.约束分为表级约束.列级约束:3.约束类型包括:NOT NULL(非空约束).PRIMARY KEY(主键约束).UNIQUE ...
- wsl下安装并运行Kafka
0.引言 kafka是一个高性能分布式的MQ,今天我们就来玩玩 1.安装 wget http://mirrors.tuna.tsinghua.edu.cn/apache/kafka/2.3.0/kaf ...
- Outlook API
1.Outlook简介 若要从Outlook 外控制Outlook对象,必须在编写代码的工程中建立对Outlook对象库的引用. 1.1 Outlook Application说明: 代表整个Mic ...
- GCN
REFERENCE: https://www.jianshu.com/p/ad528c40a08f https://www.zhihu.com/question/54504471 图有两个基本的特性: ...
- Python从零开始——集合Set
一:Python集合知识概览 二:Python的特性.格式.以及各序列结构对比 三:Python集合set的创建 四:集合常用操作之——添加元素 五:集合常见操作之——删除元素 六:集合常见操作之—— ...