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

闭包晦涩的定义

看过很多关于闭包的定义,很多讲的云里雾里,晦涩难懂。让不少人以为闭包是多么玄乎的东西。在我看过的所有书籍中,我更喜欢《你不知道的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. webrtc笔记(2): 1对1实时视频/语音通讯原理概述

    开始正文之前,先思考1个问题:2个处于不同网络环境的(具备摄像头/麦克风多媒体设备的)浏览器,要实现点对点的实时视频/语音通讯,难点在哪? 至少得先搞定下面2个问题: 1.彼此要了解对方支持的媒体格式 ...

  2. C#.Net 使用 JsonReader/JsonWriter 高性能解析/生成 Json 文档

    Swifter.Json 是由本人编写的高性能且多功能的 Json 解析库.下图是 Swifter.Json 与 .Net 平台上的其他 Json 库性能对比: 在 Swifter.Json 近期更新 ...

  3. Oracle中如何创建数据库

    Oracle数据库的物理结构与MySQL以及SQLServer有着很大的不同.在使用MySQL或SQLServer时,我们不需要去关心它们的逻辑结构和物理结构. 但是在使用Oracle的时候,我们必须 ...

  4. phpize安装PHP扩展

    安装编译完成php源码后忘记安装一些扩展可以通过phpize来安装 拿lnmp1.6安装举例 安装完成lnmp后发现有些扩展没有 lnmp1.6的安装脚本会在lnmp1.6里生成src,里面是lnmp ...

  5. Luogu P3577 [POI2014]TUR-Tourism

    Luogu P3577 [POI2014]TUR-Tourism 题目链接 题目大意:给出一张\(n\)个点,\(m\)条边的无向图,保证任意两点之间没有点数超过\(10\)的简单路径.选择第\(i\ ...

  6. 构建Shiny应用

    构建Shiny应用 1.什么是Shiny? Shiny是一个R的应用包,帮助用户构建可交互的web应用.它可以结合HTML和CSS代码,以及R 语言的运算能力. 2.下载R Shiny 下载R包 in ...

  7. java架构之路-(JVM优化与原理)JVM的运行时内存模型

    还是我们上次的图,我们上次大概讲解了类加载子系统的执行过程,验证,准备,解析,初始化四个过程.还有我们的双亲委派机制. 我们这次来说一下运行时内存模型.上一段小代码. public class Mai ...

  8. go中interface空指针不为nil判断方法

    interface空指针不为nil 当把一个空指针对象赋值给一个interface后,再判断!= nil就不再成立了 代码如下 package main import "fmt" ...

  9. asp.net发布后其他电脑部署——未能加载文件或程序集 System.Web.Mvc, Version=2.0.0.0, Culture=neutral,

    本机测试及发布使用正常 在项目中添加了引用但相关dll文件未在bin文件夹中 导致发布后部署其他电脑未能加载程序集 网上说要下载相关内容在部署服务器安装 但怕在服务器安装出现其他问题 所以在项目中重新 ...

  10. ASP.NET MVC IOC 之 Autofac 系列开篇

    本系列主要讲述Autofac在.NET MVC项目以及webform中的使用. autofac为IOC组件,实现控制反转,主要结合面向接口编程,完成较大程度的解耦工作. 作为初学者,将学习到的每一步, ...