知识点

1.实质问题

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

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

根据前面的定义,严格来说上述代码并不是闭包,最准确地用来解释bar()对a的引用的方法是词法作用域的查找规则,而这些规则只是闭包的一部分。

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

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

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

在foo()执行后,通常会期待foo()的整个内部作用域都被销毁,因为我们知道引擎有垃圾回收器来释放不再使用的内存空间。而闭包的“神奇”之处正是可以阻止这件事情的发生。事实上内部作用域依然存在,因此没有被回收,因为bar()本身在使用。

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

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

闭包使得函数可以继续访问定义时的词法作用域。当然,无论使用何种方式对函数类型的值进行传递,当函数在别处被调用时都可以观察到闭包。

function foo(){var a = 2;function baz(){console.log(a);}bar(baz);
}
function bar(fn){fn(); // 这就是闭包
}
复制代码

把内部函数baz传递给bar,当调用这个内部函数时(现在叫作fn),它涵盖的foo()内部作用域的闭包就可以观察到了,因为它能够访问a。

传递函数当然也可以是间接的。

var fn ;
function foo(){var a = 2;function baz(){console.log(a);}fn = baz; // 将baz分配给全局变量
}
function bar(){fn(); // 这就是闭包
}
复制代码

无论通过何种手段将内部函数传递到所在的词法作用域以外,它都会持有对原始定义作用域的引用,无论在何处执行这个函数都会使用闭包。

2.现在我懂了

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

将一个内部函数(名为timer)传递给setTimeout(..)。timer具有涵盖wait(..)作用域的闭包,因此还保有对变量message的引用。

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

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

3.循环和闭包

for(var i = 1;i <= 5; i++){setTimeout(function timer(){console.log(i); // 每秒一次的频率输出5次6},i*1000)
}
复制代码

根据作用域的工作原理,实际情况是尽管循环中的五个函数是在各个迭代中分别定义的,但是它们都被封闭在一个共享的全局作用域中,因此实际上只有一个i。

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

在迭代内使用IIFE会为每个迭代都生成一个新的作用域,使得延迟函数的回调可以将新的作用域封闭在每个迭代内部,每个迭代中都会含有一个具有正确值的变量供我们访问。

4.重返块作用域

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

5.模块

function foo(){var something = "cool";var another = [1,2,3];function doSomething(){console.log(something);}function doAnother(){console.log(another.join(" ! ")}
}
复制代码

私有数据变量something和another,以及doSomething()和doAnother()两个内部函数,它们的词法作用域(而这就是闭包)也就是foo()的内部作用域。

function CoolModule(){var something = "cool";var another = [1,2,3];function doSomething(){console.log(something);}function doAnother(){console.log(another.join("!");}return {doSomething: doSomething,doAnother: doAnother}
}var foo = CoolModule();
foo.doSomething(); // cool
foo.doAnother(); // 1!2!3
复制代码

这个模式在JavaScript中被称为模块。

模块模式需要具备两个必要条件。

1.必须有外部的封闭函数,该函数必须至少被调用一次(每次调用都会创建一个新的模块实例)。

2.封闭函数必须返回至少一个内部函数,这样内部函数才能在私有作用域中形成闭包,并且可以访问或者修改私有的状态。

一个从函数调用所返回的,只有数据属性而没有闭包函数的对象并不是真正的模块。

var foo = (function CoolModule(){var something = "cool";var another = [1,2,3];function doSomething(){console.log(something);}function doAnother(){console.log(another.join("!");}return {doSomething: doSomething,doAnother: doAnother}
})()
foo.doSomething(); // cool
foo.doAnother(); // 1!2!3
复制代码

将模块函数转换成了IIFE,立即调用这个函数并将返回值直接赋值给单例的模块实例标识符foo。

模块模式的一个简单但强大的用法是命名将要作为公共API返回的对象:

var foo = (function CoolModule(id){function change(){// 修改公共APIpublicAPI.identify = identify2;}function identify1(){console.log(id);}function identify2(){console.log(id.toUpperCase());}var publicAPI = {change: change,identify: identify1}return publicAPI;
}("foo mocule");foo.identify(); // foo module
foo.change();
foo.identify(); // FOO MODULE
复制代码

通过在模块实例的内部保留对公共API对象的内部引用,可以从内部对模块实例进行修改,包括添加或删除方法和属性,以及修改它们的值。

总结

我们在词法作用域的环境下写代码,而其中的函数也是值,可以随意传来传去。

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

闭包是一个非常强大的工具,可以用多种形式来实现模块等模式。

模块有两个主要特征:

  • 1.为创建内部作用域而调用了一个包装函数
  • 2.包装函数的返回值必须至少包括一个对内部函数的引用,这样就会创建涵盖整个包装函数内部作用域的闭包。

巴拉巴拉

关于脑子一热

我的经历告诉我,脑子一热做的事情,多半会后悔,而且会非常后悔。但是怎么去避免呢,方法我还没找到,每次我遇到这样的情绪,都会找各种理由去逃避,这是目前我的低级应对措施,相当低级。如果能从根源消除是最好不过的了,可是我还没有那么大的控制力,所以只能慢慢去培养,尽量减少这种上头的次数了。

转载于:https://juejin.im/post/5d0a363a51882563194b31f2

《你不知道的JavaScript》-- 精读(五)相关推荐

  1. 精读《你不知道的javascript》中卷

    前言 <你不知道的 javascript>是一个前端学习必读的系列,让不求甚解的JavaScript开发者迎难而上,深入语言内部,弄清楚JavaScript每一个零部件的用途.本书< ...

  2. 精读《你不知道的 javascript(上卷)》

    前言 <你不知道的 javascript>是一个前端学习必读的系列,让不求甚解的JavaScript开发者迎难而上,深入语言内部,弄清楚JavaScript每一个零部件的用途.本书介绍了该 ...

  3. JS闭包—你不知道的JavaScript上卷读书笔记(二)

    关于闭包,初学者会被绕的晕头转向,在学习的路上也付出了很多精力来理解. 让我们一起来揭开闭包神秘的面纱. 闭包晦涩的定义 看过很多关于闭包的定义,很多讲的云里雾里,晦涩难懂.让不少人以为闭包是多么玄乎 ...

  4. 读书笔记-你不知道的JavaScript(上)

    本文首发在我的个人博客:http://muyunyun.cn/ <你不知道的JavaScript>系列丛书给出了很多颠覆以往对JavaScript认知的点, 读完上卷,受益匪浅,于是对其精 ...

  5. 你不知道的JavaScript 上卷 Part1

      这篇博客躺在我的草稿箱里有一阵子了,差点给遗忘了哈哈. 前言   最近开始喜欢读一些书,从书中找答案,在阅读中查漏补缺.   记得小学初中时候最爱看书了,如今却不知怎的,习惯性从网络中摄取知识,搜 ...

  6. JavaScript笔记 | 作用域和闭包 |《你不知道的JavaScript(上卷)》第一部分

    JavaScript | 作用域和闭包 | 读书笔记 读书笔记(自用) 来自<你不知道的JavaScript(上卷)>第一部分 作用域和闭包 1 作用域是什么 1.1编译的3个步骤 (1) ...

  7. 《你不知道的 JavaScript》上卷之作用域和闭包

    <你不知道的 JavaScript>是一个前端学习必读的系列,让不求甚解的JavaScript开发者迎难而上,深入语言内部,弄清楚JavaScript每一个零部件的用途.这本书介绍了该系列 ...

  8. 你不知道的javascript(上卷)----读书笔记

    <!DOCTYPE html> <html><head><meta charset="utf-8"><title>你不知 ...

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

    概述 这是我看<你不知道的JavaScript(中卷)>中关于类型检查的笔记,供以后开发时参考,相信对其他人也有用. typeof 我们知道js中有七种内置类型:undefined, nu ...

  10. 十分钟快速了解《你不知道的 JavaScript》(上卷)

    最近刚刚看完了<你不知道的 JavaScript>上卷,对 JavaScript 有了更进一步的了解. <你不知道的 JavaScript>上卷由两部分组成,第一部分是< ...

最新文章

  1. 汇编环境搭建(vs2010(2012)+masm32)
  2. python 内网镜像站_搭建私有YUM仓库与内网镜像站
  3. “达观杯”文本智能处理挑战赛,季军带你飞
  4. html前端如何缓存页面,Nuxt中如何做页面html缓存
  5. 项目review会议的步骤_进行完美的30分钟会议的6个步骤
  6. linux环境变量权限不够,linux环境变量及权限的理解
  7. 网络从业人员职业规划路线图
  8. Java中List.forEach()方法使用
  9. keyshot分辨率多少合适_分辨率单位及换算详解
  10. 【Python学习】最新版pyecharts之绘制Map地图
  11. Node.js开发入门—使用http访问外部世界
  12. QT图形显示和处理6
  13. nginx: [emerg] bind() to 0.0.0.0:80 failed (13: Permission denied)
  14. 人工智能真正值得担心的是缺德,而不是聪明
  15. 达梦数据库在ZYJ环境上通过RPM打包注册服务的步骤
  16. 【计导非课系列】 第五节 二进制 进制计算 编码
  17. 【火车票】心蓝抢票软件
  18. 01 A股10个月争取翻10倍实盘操作记录(前言)
  19. 华为公司员工待遇全面揭秘 选择自 CQP 的 Blog
  20. Web组成:静态网页,动态网页

热门文章

  1. boost::mp11::mp_or相关用法的测试程序
  2. boost::hana::permutations用法的测试程序
  3. Boost:bimap双图的序列化的测试程序
  4. VTK:可视化之VectorText
  5. VTK:结构化网格之VisualizeStructuredGridCells
  6. VTK:PolyData之ExtractCellsUsingPoints
  7. Qt Creator在设计模式下编辑3D资产
  8. OpenGL Shadow Mapping阴影贴图的实例
  9. OpenGL绘制Triangle三角形
  10. c++Binary search二分法检索(折半检索)的实现算法(附完整源码)