JavaScript 深入之闭包

2017/05/21 · JavaScript
· 闭包

原文出处: 冴羽   

前端基础进阶(四):详细图解作用域链与闭包

2017/02/24 · 基础技术 ·
作用域链,
闭包

原文出处: 波同学   

澳门微尼斯人手机版 1

攻克闭包难题

初学JavaScript的时候,我在学习闭包上,走了很多弯路。而这次重新回过头来对基础知识进行梳理,要讲清楚闭包,也是一个非常大的挑战。

闭包有多重要?如果你是初入前端的朋友,我没有办法直观的告诉你闭包在实际开发中的无处不在,但是我可以告诉你,前端面试,必问闭包。面试官们常常用对闭包的了解程度来判定面试者的基础水平,保守估计,10个前端面试者,至少5个都死在闭包上。

可是为什么,闭包如此重要,还是有那么多人没有搞清楚呢?是因为大家不愿意学习吗?还真不是,而是我们通过搜索找到的大部分讲解闭包的中文文章,都没有清晰明了的把闭包讲解清楚。要么浅尝辄止,要么高深莫测,要么干脆就直接乱说一通。包括我自己曾经也写过一篇关于闭包的总结,回头一看,不忍直视[捂脸]。

因此本文的目的就在于,能够清晰明了得把闭包说清楚,让读者老爷们看了之后,就把闭包给彻底学会了,而不是似懂非懂。

连不上网?英国卫报的个性离线页面是这样做的

2015/11/20 · HTML5 · Service
Worker,
离线页面

本文由 伯乐在线 –
Erucy
翻译,weavewillg
校稿。未经许可,禁止转载!
英文出处:Oliver
Ash。欢迎加入翻译组。

我们是如何使用 service worker 来为 theguardian.com
构建一个自定义的离线页面。

澳门微尼斯人手机版 2

theguardian.com 的离线页面。插图:Oliver Ash

你正在通往公司路上的地铁里,在手机上打开了
Guardian
应用。地铁被隧道包围着,不过这个应用可以如常运行,即使没有网络连接,你也能获得完整的功能,除了显示的内容可能有点旧。如果你尝试在网站上也这么干,可惜它完全没法加载:

澳门微尼斯人手机版 3

安卓版 Chrome 的离线页面

Chrome 中的这个彩蛋,很多人都不知道》

Chrome
在离线页面上有个隐藏的游戏(桌面版上按空格键,手机版上点击那只恐龙),这多少能减轻一点你的烦躁。不过我们可以做得更好。

Service
workers
允许网站作者拦截自己站点的所有网络请求,这也就意味着我们可以提供完善的离线体验,就像原生应用一样。在
Guardian
网站,我们最近上线了一个自定义的离线体验功能。当用户离线的时候,他们会看到一个带有
Guardian
标识的页面,上面带有一个简短的离线提示,还有一个填字游戏,他们可以在等待网络连接的时候玩玩这个找点乐子。这篇博客解释了我们是如何构建它的,不过在开始之前,你可以先自己试试看。

定义

MDN 对闭包的定义为:

闭包是指那些能够访问自由变量的函数。

那什么是自由变量呢?

自由变量是指在函数中使用的,但既不是函数参数也不是函数的局部变量的变量。

由此,我们可以看出闭包共有两部分组成:

闭包 = 函数 + 函数能够访问的自由变量

举个例子:

var a = 1; function foo() { console.log(a); } foo();

1
2
3
4
5
6
7
var a = 1;
 
function foo() {
    console.log(a);
}
 
foo();

foo 函数可以访问变量 a,但是 a 既不是 foo 函数的局部变量,也不是 foo
函数的参数,所以 a 就是自由变量。

那么,函数 foo + foo 函数访问的自由变量 a 不就是构成了一个闭包嘛……

还真是这样的!

所以在《JavaScript权威指南》中就讲到:从技术的角度讲,所有的JavaScript函数都是闭包。

咦,这怎么跟我们平时看到的讲到的闭包不一样呢!?

别着急,这是理论上的闭包,其实还有一个实践角度上的闭包,让我们看看汤姆大叔翻译的关于闭包的文章中的定义:

ECMAScript中,闭包指的是:

  1. 从理论角度:所有的函数。因为它们都在创建的时候就将上层上下文的数据保存起来了。哪怕是简单的全局变量也是如此,因为函数中访问全局变量就相当于是在访问自由变量,这个时候使用最外层的作用域。
  2. 从实践角度:以下函数才算是闭包:
    1. 即使创建它的上下文已经销毁,它仍然存在(比如,内部函数从父函数中返回)
    2. 在代码中引用了自由变量

接下来就来讲讲实践上的闭包。

一、作用域与作用域链

在详细讲解作用域链之前,我默认你已经大概明白了JavaScript中的下面这些重要概念。这些概念将会非常有帮助。

  • 基础数据类型与引用数据类型
  • 内存空间
  • 垃圾回收机制
  • 执行上下文
  • 变量对象与活动对象

如果你暂时还没有明白,可以去看本系列的前三篇文章,本文文末有目录链接。为了讲解闭包,我已经为大家做好了基础知识的铺垫。哈哈,真是好大一出戏。

作用域

  • 在JavaScript中,我们可以将作用域定义为一套规则,这套规则用来管理引擎如何在当前作用域以及嵌套的子作用域中根据标识符名称进行变量查找。

    这里的标识符,指的是变量名或者函数名

  • JavaScript中只有全局作用域与函数作用域(因为eval我们平时开发中几乎不会用到它,这里不讨论)。

  • 作用域与执行上下文是完全不同的两个概念。我知道很多人会混淆他们,但是一定要仔细区分。

    JavaScript代码的整个执行过程,分为两个阶段,代码编译阶段与代码执行阶段。编译阶段由编译器完成,将代码翻译成可执行代码,这个阶段作用域规则会确定。执行阶段由引擎完成,主要任务是执行可执行代码,执行上下文在这个阶段创建。

澳门微尼斯人手机版 4

过程

作用域链

回顾一下上一篇文章我们分析的执行上下文的生命周期,如下图。

澳门微尼斯人手机版 5

执行上下文生命周期

我们发现,作用域链是在执行上下文的创建阶段生成的。这个就奇怪了。上面我们刚刚说作用域在编译阶段确定规则,可是为什么作用域链却在执行阶段确定呢?

之所有有这个疑问,是因为大家对作用域和作用域链有一个误解。我们上面说了,作用域是一套规则,那么作用域链是什么呢?是这套规则的具体实现。所以这就是作用域与作用域链的关系,相信大家都应该明白了吧。

我们知道函数在调用激活时,会开始创建对应的执行上下文,在执行上下文生成的过程中,变量对象,作用域链,以及this的值会分别被确定。之前一篇文章我们详细说明了变量对象,而这里,我们将详细说明作用域链。

作用域链,是由当前环境与上层环境的一系列变量对象组成,它保证了当前执行环境对符合访问权限的变量和函数的有序访问。

为了帮助大家理解作用域链,我我们先结合一个例子,以及相应的图示来说明。

JavaScript

var a = 20; function test() { var b = a + 10; function innerTest() { var
c = 10; return b + c; } return innerTest(); } test();

1
2
3
4
5
6
7
8
9
10
11
12
13
14
var a = 20;
 
function test() {
    var b = a + 10;
 
    function innerTest() {
        var c = 10;
        return b + c;
    }
 
    return innerTest();
}
 
test();

在上面的例子中,全局,函数test,函数innerTest的执行上下文先后创建。我们设定他们的变量对象分别为VO(global),VO(test),
VO(innerTest)。而innerTest的作用域链,则同时包含了这三个变量对象,所以innerTest的执行上下文可如下表示。

JavaScript

innerTestEC = { VO: {…}, // 变量对象 scopeChain: [VO(innerTest),
VO(test), VO(global)], // 作用域链 this: {} }

1
2
3
4
5
innerTestEC = {
    VO: {…},  // 变量对象
    scopeChain: [VO(innerTest), VO(test), VO(global)], // 作用域链
    this: {}
}

是的,你没有看错,我们可以直接用一个数组来表示作用域链,数组的第一项scopeChain[0]为作用域链的最前端,而数组的最后一项,为作用域链的最末端,所有的最末端都为全局变量对象。

很多人会误解为当前作用域与上层作用域为包含关系,但其实并不是。以最前端为起点,最末端为终点的单方向通道我认为是更加贴切的形容。如图。

澳门微尼斯人手机版 6

作用域链图示

注意,因为变量对象在执行上下文进入执行阶段时,就变成了活动对象,这一点在上一篇文章中已经讲过,因此图中使用了AO来表示。Active
Object

是的,作用域链是由一系列变量对象组成,我们可以在这个单向通道中,查询变量对象中的标识符,这样就可以访问到上一层作用域中的变量了。

试试看

你需要一个支持 Service
Worker 和 fetch
API 的浏览器。截止到本文编写时只有
Chrome(手机版和桌面版)同时支持这两种 API(译者注:Opera
目前也支持这两者),不过 Firefox
很快就要支持了(在每日更新的版本中已经支持了),除了 Safari
之外的所有浏览器也都在跃跃欲试。此外,service worker 只能注册在使用了
HTTPS 的网站上,theguardian.com
已经开始逐步迁移到 HTTPS,所以我们只能在网站的 HTTPS
部分提供离线体验。就目前来说,我们选择了 开发者博客 作为我们用来测试的地方。所以如果你是在我们网站的 开发者博客 部分阅读这篇文章的话,很走运。

当你使用支持的浏览器访问我们的 开发者博客 中的页面的时候,一切就准备妥当了。断开你的网络连接,然后刷新一下页面。如果你自己没条件尝试的话,可以看一下这段 演示视频(译者注:需梯子)。

发表评论

电子邮件地址不会被公开。 必填项已用*标注