深入理解JavaScript闭包

原文链接:Understanding JavaScript: Closures

为什么要深入学习JavaScript

JavaScript是当今世界最流行的一种编程语言。它可以在浏览器运行,也可以在服务器端运行,可以在移动设备,桌面设备,甚至冰箱上面运行。当我们在下载异国风情的图片,当你在处理任何类型的web开发,你都会在某个时刻编写或者处理JavaScript。

许多的web开发者声称他们知道JavaScript,因为他们能够编写可运行的代码。JS是一个你可以一个月入门写代码,但是余生都要不停学习掌握的语言。如果没有出错,没人会抱怨为什么你需要了解更多。

然而我感觉我已经够深入了解JS了。几年前我使用AngularJS和Node开发APP,而且我对我的技能非常的自信。把方法调来调去,相信自己已经征服了JS。

所以当面试官让我解释一下什么是闭包的时候,我有点懵逼了。我意思是我有点了解。我知道它和回调有关,而我总是在使用回调(当时我并不知道Promise),但是,我找不到词语去解释它,以及它是怎样工作的。

一次失败的JavaScript面试在我的开发生涯中让我感到非常难受且很受教。从那时起,我花了一年半的时间去达到JS更高级别的段位,而现在是我与大家分享它的时候了。从JavaScript最常见的面试题开始:

什么是闭包?

毫无疑问,闭包有各种各样的使用,我们也经常用到闭包。每次在给事件添加回调处理的时候,都会用到闭包。

我见过好几种关于闭包的一句话的解释,但点击最多的是Kyle Simpson给出的:

Closure is when a function is able to remember and access its lexical scope even when that function is executing outside its lexical scope.

当函数在它的词法范围外执行的时候,仍然能够访问、使用它的词法范围内的数据。

(词法范围是什么呢?词法范围有时候又叫做静态范围,是很多编程语言都使用的一种规定。它设置变量的功能范围,使得只能够在定义它的代码快中使用。此范围是在代码编译时期确定的。 这种方式声明的变量经常叫做*私有变量)

这个解释可能有点笼统,所以我们要一点一点的拆开它,看看它并不想是黑魔法那样。

本文不会详细讨论作用域(将会有文档单独讨论),但是知道作用域对于理解闭包是如何工作的是很有必要的。闭包实际上是包含变量和函数的代码的一部分。在JavaScript中,每一个函数都创建一个新的作用域,它的变量和传递的参数只能在它的作用域里面使用。

如果你在一个函数内部声明了一个变量,那么该变量在函数外部不可访问。但是我们可以在函数中定义函数,这些函数在函数内部作用域有效。现在这些特殊的嵌套函数可以访问他们父函数的变量。

这并没有什么特殊的,因为全局作用域定义的函数都能够访问它们自己的变量,但是这里也有点东西。这些切套函数可以访问它们父函数的作用域,但是本身不能从父函数外部调用,除非我们以某种方式暴露出来。

我们可以把这些内部函数暴露出来,这样就可以在全局作用域使用。现在我们可以随心所欲的使用这些内部函数。但是,我们假设这个暴露的内部函数引用它的函数作用域中的一个变量。 这会有问题吗? 不会,因为这就是一个闭包。

闭包是暴露出来的嵌套函数。

我不确定这是不是关于闭包的最好的定义,但是提现出了闭包的精髓。闭包是暴露出来的嵌套函数,所以我们可以在外部访问这些函数的父函数的作用域。现在你理解我们前面所说的词法范围了吗?

现在我们定义一个名为person的函数,有一个名为name的参数,此函数范围一个函数对象greet,现在我们知道,当我们调用这个暴露出来的函数greet可以访问person的参数。所以即便变量name不是在greet中声明的,但是greet可以访问到它,因为这是一个闭包。

functionperson() {

  varname="beauty";


functiongreet() {

return"Hello "+name;

  }


returngreet;

}

你可以在很多时候用使用这种特性,在我不了解闭包之前,我没想过背后的黑魔法,所以我能够使用封装和模块。

哈,哈,...模块?封装? 那些突然出现了。

闭包带来的模块和封装

Modules and encapsulation with Closures

当我深陷Javascript的漩涡中后,我发现很多复杂的此货都可以通过实践来解释。模块和风中是最好的离职,我们可以使用相同的策略一个一个拆开理解,我们从封装开始。

封装是编程的基本原则之一。研究过OOP的人对这块很熟悉,对那些不熟悉OOP的人来说,这是一种隐藏机制,它允许我们将一些数据设置为私有的。我们通常不希望将函数所有内容暴露出来,而是希望将函数的大部分属性都设置为私有的,不能被随便访问。

这是闭包很强大的地方,我们可以使用闭包访问父作用域使得在外部被调用的时候,实现封装。父函数中可能有很多变量和函数,我们可以在外部暴露出来使得可以被广泛的使用。

通过闭包,我们可以给我们的函数定义一个公共API,而其他的内部数据保持私有。

现在我们需要通过多实践来使用封装。接下来就是拆解模块化了。

模块

在ES6里面我们可以使用import和export关键字实现基于文件的模块化,但是我们应该意识到这紧紧是语法糖而已。

这是一个相当简单的例子,用于演示如何将某些函数的数据保密。我们可以使用暴露出来的嵌套函数在其他地方使用这些私有的数据。

在这个稍微更现实的例子中,我们有一个返回订单对象的函数。 唯一暴露的函数是calculateTotal。 它有一个订单函数的闭包,可以使用变量并传递参数,隐藏了计算订单总额时的内部逻辑,且允许以后添加运费或者其他逻辑。

在将来添加运费或其他内容。

独特之处

JavaScript有它的独特之处,事实上,有些比较特殊的地方真的是够让人头疼,并且大晚上调试代码。然而当我们使用不当的话,也不会有啥大问题。

一下代码经常会出现在JavaScript面试中,我们猜猜它的输出是什么。

我们这里所做的就是从1到5循环,并设置超时时间,使得在特定时间后打印当前数字,这段代码会打印出1,2,3,4,5吗?

for(vari=1;i<=5;i++) {

setTimeout(functiontimer() {

console.log(i)

},i*1000);

}

而且,让我们吃惊的是,最后打印出来的结果是5个6,如果没在setTimeout中执行,可能不会有疑问,因为log会立即执行,但是显然对操作排队是造成这个结果的原因。

我们期望每一个setTimeout调用接收到它自己的i变量的一份拷贝,但是实际上会从父作用域访问它。并且因为排队的原因,第一个log调用将会在排队后1秒执行,而当1秒过去后,循环早就执行完了,而这个时候i变量的值已经是6了。

知道原因后怎么解决呢?因为setTimeout在全局范围内查找i变量,导致不打印我们想要的结果,我们可以将setTimeout封装在函数中并传给循环的变量i,这样setTimeout函数将从它的父作用域而不是作用域访问i。

for(vari=1;i<=5;i++) {

(function(i){

setTimeout(functiontimer() {

console.log(i)

},i*1000)

})(i);

}

我们使用立即执行函数表达式(IIFE  Immediately Invoked Function Expression),并将参数i传给此函数。顾名思义,IIFE就是在定义后立即执行的函数,就像我们这种用法一样可以创建一个作用域。每个函数被调用的时候,都有它自己的变量副本,setTimeout执行的时候,能够访问到正确的i。所以,使用上面的例子,能够打印出我们想要的结果(1,2,3,4,5)。

关于闭包的思考

本文揭示了闭包的本质,但是关于闭包还有很多东西和大量案例需要去学习思考。如果你想更深入探讨闭包的强大用法,我强烈介意Kyle Simpson的书《Scope & Closures》

我希望本文能够提高您对JavaScript的理解,并帮助你更好的理解闭包。如果觉得还可以,可以给你的朋友分享这篇文章。

如果您对更多JS相关内容感兴趣,可以点击这里订阅我的更多资讯,或者可以查看这个系列中的其他文章。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,098评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,213评论 2 380
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,960评论 0 336
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,519评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,512评论 5 364
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,533评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,914评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,574评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,804评论 1 296
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,563评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,644评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,350评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,933评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,908评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,146评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,847评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,361评论 2 342

推荐阅读更多精彩内容