JS中有一个非常重要但又难以完全掌握的概念,那就是闭包。很多JS程序员自以为已经掌握了闭包,但实质上是一知半解,就像“JS中万物皆为对象”这个常见的错误说法一样,很多前端开发者到现在还固执己见。对于闭包,笔者现在也不敢说是完全掌握,但是希望通过自己对闭包的理解,让更多人对闭包的概念有一个更深刻的认识。

理解闭包的重要性,我想用一本书中的话来描述:“对于那些有一点JavaScript使用经验但从未真正理解闭包概念的人来说,理解闭包可以看作是某种意义上的重生”。

一、概念

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

2、下面一段代码,清晰地展示了闭包:

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()依然持有对该作用域的引用,而这个引用就叫作闭包。

因此,在几微秒之后变量baz被实际调用(调用内部函数bar),不出意料它可以访问定义时的词法 作用域,因此它也可以如预期般访问变量a。 这个函数在定义时的词法作用域以外的地方被调用。闭包使得函数可以继续访问定义时的词法作用域。

当然,无论使用何种方式对函数类型的值进行传递,当函数在别处被调用时都可以观察到闭包。

function foo() {

var a = 2;

function baz() {

console.log( a ); // 2

}

bar( baz );

}

function bar(fn) {

fn(); // 看,这就是闭包!

}

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

var fn;

function foo() {

var a = 2;

function baz() {

console.log( a );

}

fn = baz; // 将baz分配给全局变量

}

function bar() {

fn(); // 看,这就是闭包!

}

foo();

bar(); // 2

3、深入理解

闭包绝不仅仅是一个好玩的玩具。你已经写过的代码中一定到处都是闭包的身影。现在让我们来 搞懂这个事实。

function wait(message) {

setTimeout( function timer() {

console.log( message ); }, 1000 );

}

wait( "Hello, closure!" );

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

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

或者,如果你很熟悉jQuery(或者其他能说明这个问题的JavaScript框架),可以思考下面的代码:

function setupBot(name, selector) {

$( selector ).click(

function activator() {

console.log( "Activating:" + name ); } );

}

setupBot( "Closure Bot 1", "#bot_1" );

setupBot( "Closure Bot 2", "#bot_2" );

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

二.闭包的应用(或表现)

只知道闭包是什么还不够,要知道它的使用场景或说表现方式:

1、  要说明闭包,for循环是最常见的例子。

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

(function(j) {

setTimeout(

function timer() {

console.log( j );

}, j*1000 ); })( i );

}

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

let j = i; // 是的,闭包的块作用域!

setTimeout( function timer() {

console.log( j ); }, j*1000 );

}

for循环头部的let声明还会有一个特殊的行 为。这个行为指出变量在循环过程中不止被声明一次,每次迭代都会声明。随后的每个迭代都会使 用上一个迭代结束时的值来初始化这个变量。

块作用域和闭包联手便可天下无敌。

2、  模块

还有其他的代码模式利用闭包的强大威力,但从表面上看,它们似乎与回调无关。下面一起来研究 其中最强大的一个:模块。

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

一个具有函数属性的对象本身并不是真正的模块。从方便观察的角度看,一个从函数调用所返回 的,只有数据属性而没有闭包函数的对象并不是真正的模块。

三、总结

闭包就好像从JavaScript中分离出来的一个充满神秘色彩的未开化世界,只有最勇敢的人才能够 到达那里。但实际上它只是一个标准,显然就是关于如何在函数作为值按需传递的词法环境中书 写代码的。

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

如果没能认出闭包,也不了解它的工作原理,在使用它的过程中就很容易犯错,比如在循环中。但 同时闭包也是一个非常强大的工具,可以用多种形式来实现模块等模式。

模块有两个主要特征:(1)为创建内部作用域而调用了一个包装函数;(2)包装函数的返回值必须至 少包括一个对内部函数的引用,这样就会创建涵盖整个包装函数内部作用域的闭包。

现在我们会发现代码中到处都有闭包存在,并且我们能够识别闭包然后用它来做一些有用的事!

深入JS——理解闭包可以看作是某种意义上的重生的更多相关文章

  1. js 理解闭包

    学习Javascript闭包(Closure) 引用: 阮一峰 http://www.ruanyifeng.com/blog/2009/08/learning_javascript_closures. ...

  2. 我从没理解js的闭包,直到他人向我这么解释。。。

    前段时间根据执行上下文写过一次闭包,但是写的简陋些.昨天在twitter上看到这篇文章,感觉背包的比喻挺恰当的.所以就翻译了. 这篇文章有些啰嗦,但是讲解很细,希望还是耐心看完.也欢迎指出错误. 原地 ...

  3. 带你一分钟理解闭包--js面向对象编程

    上一篇<简单粗暴地理解js原型链--js面向对象编程>没想到能攒到这么多赞,实属意外.分享是个好事情,尤其是分享自己的学习感悟.所以网上关于原型链.闭包.作用域等文章多如牛毛,很多文章写得 ...

  4. 深入理解JS的闭包

    闭包(closure)是Javascript语言的一个难点,也是它的特色,很多高级应用都要依靠闭包实现. 一.变量的作用域 要理解闭包,首先必须理解Javascript特殊的变量作用域. 变量的作用域 ...

  5. 关于js中闭包的理解

    1.以前很不理解js中闭包的概念及使用,下面来看一下 function foo() { var a = 123; var b = 456; return function () { return a; ...

  6. js深入理解"闭包"

    一.变量的作用域 要理解闭包,首先必须理解Javascript特殊的变量作用域. 变量的作用域无非就是两种:全局变量和局部变量. Javascript语言的特殊之处,就在于函数内部可以直接读取全局变量 ...

  7. 前端基本知识(三):JS的闭包理解

    JS闭包的理解 一.变量的作用域 二.如何从外部读取局部变量 三.什么是闭包 四.深入理解闭包 五.闭包的用途 六.使用闭包注意情况 七.JavaScript的垃圾回收机制 八.一些思考题 一.变量作 ...

  8. 前端基本知识(三):JS的闭包理解(第一个思考题有错误,已修改)

    JS闭包的理解 一.变量的作用域 二.如何从外部读取局部变量 三.什么是闭包 四.深入理解闭包 五.闭包的用途 六.使用闭包注意情况 七.JavaScript的垃圾回收机制 八.一些思考题 一.变量作 ...

  9. 对js中闭包,作用域,原型的理解

    前几天,和朋友聊天,聊到一些js的基础的时候,有一种‘好像知道,好像又不不知道怎么讲的感觉’...于是捡起书,自己理一理,欢迎拍砖. 闭包 理解闭包首先要理解,js垃圾回收机制,也就是当一个函数被执行 ...

  10. 理解运用JS的闭包、高阶函数、柯里化

    JS的闭包,是一个谈论得比较多的话题了,不过细细想来,有些人还是理不清闭包的概念定义以及相关的特性. 这里就整理一些,做个总结. 一.闭包 1. 闭包的概念 闭包与执行上下文.环境.作用域息息相关 执 ...

随机推荐

  1. c/c++零基础坐牢第一天

    c/c++从入门到入土(1) 开始时间2023-04-12 22:37:21 结束时间2023-04-13 00:02:26 前言:恭喜大家打开信息时代的大门,每个时代都有代表性的炫酷技能:原始时代的 ...

  2. 记一次 .NET某医疗器械清洗系统 卡死分析

    一:背景 1. 讲故事 前段时间协助训练营里的一位朋友分析了一个程序卡死的问题,回过头来看这个案例比较经典,这篇稍微整理一下供后来者少踩坑吧. 二:WinDbg 分析 1. 为什么会卡死 因为是窗体程 ...

  3. 最新版新款影视直播粉红色UI的CMS源码/带教程/支付已接

    demo软件园每日更新资源,请看到最后就能获取你想要的: 1.最新版新款影视直播粉红色UI的麻豆CMS源码/带教程/支付已接 基于苹果CMS v10影视系统框架开发的前端模板,带会员中心,可设置试看付 ...

  4. Springboot整合Flowable6.x导出bpmn20

    项目源码仓库 BPMN2.0(Business Process Model and Notation)是一套业务流程模型与符号建模标准,以XML为载体,以符号可视化业务,支持精准的执行语义来描述元素的 ...

  5. 用C++编写一个简单的发布者和订阅者

    摘要:节点(Node)是通过 ROS 图进行通信的可执行进程. 本文分享自华为云社区<编写一个简单的发布者和订阅者>,作者: MAVER1CK . @[toc] 参考官方文档:Writin ...

  6. CS144 计算机网络 Lab4:TCP Connection

    前言 经过前面几个实验的铺垫,终于到了将他们组合起来的时候了.Lab4 将实现 TCP Connection 功能,内部含有 TCPReceiver 和 TCPSender,可以与 TCP 连接的另一 ...

  7. cefsharp学习笔记

    环境:VS2015+cefsharp 57.0 全部代码如下: 1.要初始化,否则不能刷新 using System; using CefSharp; using System.Collections ...

  8. #Powerquery 利用M函数合并文件(CSV、Text、Xlsx)

    在日常工作中,我们往往会遇到多个文件需要合并的情况,本文一起探讨一下利用M函数合并文件的案例. 由于需要合并的文件的格式不同,也需要选择不同的M函数来进行合并,本文将分享三个格式的合并案例. 首先介绍 ...

  9. 文心一言 VS chatgpt (11)-- 算法导论2.3 7题 3.1 4~5题

    四.2 ^ (n+1)=O(2 ^ n)成立吗? 2^(2n)=O(2 ^ n)成立吗? 文心一言: chatgpt: 第一个式子不成立,第二个式子成立. 2 ^ (n+1) = O(2 ^ n) 该 ...

  10. 2021-07-09:股票问题6。给定一个整数数组 prices,其中第 i 个元素代表了第 i 天的股票价格 ;整数 fee 代表了交易股票的手续费用。你可以无限次地完成交易,但是你每笔交易都需要付

    2021-07-09:股票问题6.给定一个整数数组 prices,其中第 i 个元素代表了第 i 天的股票价格 :整数 fee 代表了交易股票的手续费用.你可以无限次地完成交易,但是你每笔交易都需要付 ...