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

  1. function wait(message) {
  2. setTimeout( function timer() {
  3. console.log( message );
  4. }, 1000 );
  5. }
  6. wait( "Hello, closure!" );

将一个内部函数( 名为 timer) 传递给 setTimeout(..)。 timer 具有涵盖 wait(..) 作用域的闭包, 因此还保有对变量 message 的引用。wait(..) 执行 1000 毫秒后, 它的内部作用域并不会消失, timer 函数依然保有 wait(..)作用域的闭包。

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

在定时器、 事件监听器、Ajax 请求、 跨窗口通信、 Web Workers 或者任何其他的异步(或者同步) 任务中, 只要使用了回调函数, 实际上就是在使用闭包!

模块模式

还有一种代码模式利用了闭包——模块

  1. function CoolModule() {
  2. var something = "cool";
  3. var another = [1, 2, 3];
  4. function doSomething() {
  5. console.log( something );
  6. }
  7. function doAnother() {
  8. console.log( another.join( " ! " ) );
  9. }
  10. return {
  11. doSomething: doSomething,
  12. doAnother: doAnother
  13. };
  14. }
  15. var foo = CoolModule();
  16. foo.doSomething(); // cool
  17. foo.doAnother(); // 1 ! 2 ! 3

首先, CoolModule() 只是一个函数, 必须要通过调用它来创建一个模块实例。 如果不执行外部函数, 内部作用域和闭包都无法被创建。

其次, CoolModule() 返回一个用对象字面量语法 { key: value, ... } 来表示的对象。 这个返回的对象中含有对内部函数而不是内部数据变量的引用。 我们保持内部数据变量是隐藏且私有的状态。 可以将这个对象类型的返回值看作本质上是模块的公共 API

这个对象类型的返回值最终被赋值给外部的变量 foo, 然后就可以通过它来访问 API 中的属性方法, 比如 foo.doSomething()。

因此模块模式需要具备两个必要条件:

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

    实例)。
  2. 封闭函数必须返回至少一个内部函数, 这样内部函数才能在私有作用域中形成闭包, 并

    且可以访问或者修改私有的状态。

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

  1. var foo = (function CoolModule(id) {
  2. function change() {
  3. // 修改公共 API
  4. publicAPI.identify = identify2;
  5. }
  6. function identify1() {
  7. console.log( id );
  8. }
  9. function identify2() {
  10. console.log( id.toUpperCase() );
  11. }
  12. var publicAPI = {
  13. change: change,
  14. identify: identify1
  15. };
  16. return publicAPI;
  17. })( "foo module" );
  18. foo.identify(); // foo module
  19. foo.change();
  20. foo.identify(); // FOO MODULE

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

现代的模块机制

大多数模块依赖加载器 / 管理器本质上都是将这种模块定义封装进一个友好的 API。

  1. var MyModules = (function Manager() {
  2. var modules = {};
  3. function define(name, deps, impl) {
  4. for (var i = 0; i < deps.length; i++) {
  5. deps[i] = modules[deps[i]];
  6. }
  7. modules[name] = impl.apply(impl, deps);
  8. }
  9. function get(name) {
  10. return modules[name];
  11. }
  12. return {define: define, get: get};
  13. })();

这段代码的核心是 modules[name] = impl.apply(impl, deps)。 为了模块的定义引入了包装函数(可以传入任何依赖), 并且将返回值, 也就是模块的 API, 储存在一个根据名字来管理的模块列表中。

下面展示了如何用它来定义模块:

  1. MyModules.define("bar", [], function () {
  2. function hello(who) {
  3. return "Let me introduce: " + who;
  4. }
  5. return {hello: hello};
  6. });
  7. MyModules.define("foo", ["bar"], function (bar) {
  8. var hungry = "hippo";
  9. function awesome() {
  10. console.log(bar.hello(hungry).toUpperCase());
  11. }
  12. return {awesome: awesome};
  13. });
  14. var bar = MyModules.get("bar");
  15. var foo = MyModules.get("foo");
  16. console.log(bar.hello("hippo")); // Let me introduce: hippo
  17. foo.awesome(); // LET ME INTRODUCE: HIPPO

"foo" 和 "bar" 模块都是通过一个返回公共 API 的函数来定义的。 "foo" 甚至接受 "bar" 的示例作为依赖参数, 并能相应地使用它。

《你不知道的JavaScript(上)》笔记——作用域闭包的更多相关文章

  1. 你不知道的JavaScript上卷笔记

    你不知道的JavaScript上卷笔记 前言 You don't know JavaScript是github上一个系列文章   初看到这一标题的时候,感觉怎么老外也搞标题党,用这种冲突性比较强的题目 ...

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

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

  3. 《你不知道的javascript(上)》笔记

    作用域是什么 编译原理 分词/词法分析 这个过程会将由字符组成的字符串分解成(对编程语言来说)有意义的代码块,这些代码块被称为词法单元 解析/语法分析 词法单元流(数组)转换成一个由元素逐级嵌套所组成 ...

  4. 【你不知道的javaScript 上卷 笔记2】 javaScript 的作用域规则

    一.什么是词法作用域? 词法作用域是在定义词法阶段的作用域,就是由代码变量和作用域块写在哪里决定的,基本上词法分析器在处理代码时会保持作用域不变. 二.词法作用域特点 完全由写代码期间函数所声明的位置 ...

  5. <你不知道的JavaScript>读书笔记

    近几天看了一本不错的 JavaScript 的书,是 Kyle Simpson 写的 <You Don't know JS>.这本书是 Kyle Simpson 在 Github 上的开源 ...

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

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

  7. 《你不知道的JavaScript》笔记(一)

    用了一个星期把<你不知道的JavaScript>看完了,但是留下了很多疑惑,于是又带着这些疑惑回头看JavaScript的内容,略有所获. 第二遍阅读这本书,希望自己能够有更为深刻的理解. ...

  8. 【你不知道的javaScript 上卷 笔记3】javaScript中的声明提升表现

    console.log( a ); var a = 2; 执行输出undefined a = 2; var a; console.log( a ); 执行输出2 说明:javaScript 运行时在编 ...

  9. 【你不知道的javaScript 上卷 笔记5】javaScript中的this词法

    function foo() { console.log( a ); } function bar() { var a = 3; foo(); } var a = 2; bar(); 上面这段代码为什 ...

随机推荐

  1. require.js 加载 js 文件 404 处理(配置无效)

    main.js 是 配置文件,data-main 是异步加载,如果在main.js未加载完成的时候,使用了require去加载文件,就会导致配置无效  main.js

  2. CentOS7数据库架构之NFS+heartbeat+DRBD(亲测,详解)

    目录 参考文档 理论概述 DRBD 架构 NFS 架构部署 部署DRBD 部署heartbeat 部署NFS及配合heartbeat nfs切记要挂载到别的机器上不要为了省事,省机器 参考文档 htt ...

  3. 全文检索引擎在Django中的使用

    Haystack 1.什么是Haystack Haystack是django的开源全文搜索框架(全文检索不同于特定字段的模糊查询,使用全文检索的效率更高 ),该框架支持Solr,Elasticsear ...

  4. 排序算法(冒泡、选择)-python代码展示

    冒泡排序: def bubble_sort(list): for i in range(len(list) - 1): # 这个循环负责设置冒泡排序进行的次数 for j in range(len(l ...

  5. k2系列-安装篇

    K2介绍: K2是基于BPM的流程开发平台,它支持在net开发环境/visio/moss等不同环境下进行流程开发. K2本身部署简单,操作灵活,非常适合大中型企业流程开发和部署. K2安装步骤: 首先 ...

  6. Map集合中get不存在的key值

    返回的值是null 测试代码 import java.util.HashMap; import java.util.Map; public class Test { public static voi ...

  7. idou老师教你学Istio 23 : 如何用 Istio 实现速率限制

    使用 Istio 可以很方便地实现速率限制.本文介绍了速率限制的使用场景,使用 memquota\redisquota adapter 实现速率限制的方法,通过配置 rule 实现有条件的速率限制,以 ...

  8. SQL SERVER 2008 存储过程传表参数

      最近项目使用到了存储过程传入表类型参数. --定义表类型 create type t_table_type as table ( id int, name varchar(32), sex var ...

  9. Hadoop添加LZO压缩支持

    启用lzo的压缩方式对于小规模集群是很有用处,压缩比率大概能降到原始日志大小的1/3.同时解压缩的速度也比较快. 安装 准备jar包 1)先下载lzo的jar项目https://github.com/ ...

  10. gorm 更新数据时,0值会被忽略

    原文: https://www.tizi365.com/archives/22.html ------------------------------------------------------- ...