什么是闭包

维基百科中的概念

  • 在计算机科学中,闭包(英语:Closure),又称词法闭包(Lexical Closure)或函数闭包(function closures),是在支持头等函数的编程语言中实现词法绑定的一种技术。闭包在实现上是一个结构体,它存储了一个函数(通常是其入口地址)和一个关联的环境(相当于一个符号查找表)。环境里是若干对符号和值的对应关系,它既要包括约束变量(该函数内部绑定的符号),也要包括自由变量(在函数外部定义但在函数内被引用),有些函数也可能没有自由变量。闭包跟函数最大的不同在于,当捕捉闭包的时候,它的自由变量会在捕捉时被确定,这样即便脱离了捕捉时的上下文,它也能照常运行。捕捉时对于值的处理可以是值拷贝,也可以是名称引用,这通常由语言设计者决定,也可能由用户自行指定。
  • 闭包和匿名函数经常被用作同义词。但严格来说,匿名函数就是字面意义上没有被赋予名称的函数,而闭包则实际上是一个函数的实例,也就是说它是存在于内存里的某个结构体。如果从实现上来看的话,匿名函数如果没有捕捉自由变量,那么它其实可以被实现为一个函数指针,或者直接内联到调用点,如果它捕捉了自由变量那么它将是一个闭包;而闭包则意味着同时包括函数指针和环境两个关键元素。在编译优化当中,没有捕捉自由变量的闭包可以被优化成普通函数,这样就无需分配闭包结构体,这种编译技巧被称为函数跃升

闭包和状态表达

  • 闭包可以用来在一个函数与一组“私有”变量之间创建关联关系。在给定函数被多次调用的过程中,这些私有变量能够保持其持久性。变量的作用域仅限于包含它们的函数,因此无法从其它程序代码部分进行访问。不过,变量的生存期是可以很长,在一次函数调用期间所创建所生成的值在下次函数调用时仍然存在。正因为这一特点,闭包可以用来完成信息隐藏,进而应用于需要状态表达的某些编程范型中。

学术上

  • 闭包在 JavaScript 中是指,内部函数总是可以访问其所在的外部函数中声明的参数和变量,即使在其外部函数被返回return掉(寿命终结)了之后。

个人理解

  • 闭包是在函数里面定义一个函数,该函数可以是匿名函数,子函数能够读写父函数的局部变量

Understanding the JavaScript Closures

In the JavaScript functions chapter you've learn that in JavaScript a variable's scope can be global or local. Since ES6 you can also create block-scoped variables using the let keyword.

在JavaScript函数一章中,您已经了解到,在JavaScript中,变量的作用域可以是全局或局部的。

A global variable can be accessed and manipulated anywhere in the program, whereas a local variable can only be accessed and manipulated by the function they are declared in.

全局变量可以在程序中的任何位置访问和操作,而局部变量只能由声明它们的函数访问和操作。

However, there are certain situations when you want a variable to be available throughout the script, but you don't want just any part of your code to be able to change its value accidentally.

然而,在某些情况下,您希望变量在整个脚本中都可用,但是您不希望代码的任何部分都能意外更改其值。

Let's see what happens if you try to achieve this using the global variable:

让我们看看如果您尝试使用全局变量来实现此目标,将会发生什么:

// Global variable
var counter = 0; // A function dedicated to manipulate the 'counter' variable
// 专门用于操纵'counter'变量的函数
function makeCounter() {
return counter += 1;
} // Calling the function
makeCounter();
console.log(counter); // Prints: 1 makeCounter();
console.log(counter); // Prints: 2 // Trying to manipulate the 'counter' variable from outside
// 试图从外部操纵'counter'变量
counter = 10;
console.log(counter); // Prints: 10

As you can see in the above example, the value of the counter variable can be changed from anywhere in the program, without calling the makeCounter() function

如您在上面的示例中看到的那样,可以在程序中的任何位置更改计数器变量的值,而无需调用makeCounter()函数

Now, let's try to achieve the same thing with the local variable and see what happens

现在,让我们尝试使用局部变量来实现相同的目的,然后看看会发生什么

function makeCounter() {
// Local variable
var counter = 0; // Manipulating the 'counter' variable
return counter += 1;
} // Calling the function
console.log(makeCounter()); // Prints: 1
console.log(makeCounter()); // Prints: 1

In this case the counter variable cannot be manipulated from outside, since it is local to makeCounter() function, but its value will also not increase after subsequent function call, because every time we call the function it reset the counter variable value, which you can clearly see in the above example (line no-11). The JavaScript closure can solve our problem.

在这种情况下,无法从外部操作计数器变量,因为它是makeCounter()函数的本地变量,但在后续函数调用后其值也不会增加,因为每次调用该函数都会重置计数器变量值,您可以可以在上面的示例(第11行)中清楚地看到。JavaScript闭包可以解决我们的问题。

A closure is basically an inner function that has access to the parent function's scope, even after the parent function has finished executing. This is accomplished by creating a function inside another function. Let's take a look at the following example to see how it works:

闭包基本上是一个内部函数,即使父函数执行完毕,它也可以访问父函数的作用域。这是通过在另一个函数内部创建一个函数来实现的。让我们看下面的示例,看看它是如何工作的

function makeCounter() {
var counter = 0; // Nested function
function make() {
counter += 1;
return counter;
}
return make;
} /* Execute the makeCounter() function and store the
returned value in the myCounter variable */
// 执行makeCounter()函数并存储myCounter变量中返回的值
var myCounter = makeCounter(); console.log(myCounter()); // Prints: 1
console.log(myCounter()); // Prints: 2

As you can see in the above example, the inner function make() is returned from the outer function makeCounter(). So the value of the myCounter is the inner make() function (line no-14), and calling myCounter effectively calls make(). In JavaScript functions can assigned to variables, passed as arguments to other functions, can be nested inside other functions, and more.

如上例所示,内部函数make()是从外部函数makeCounter()返回的。因此,myCounter的值是内部的make()函数(第14行),调用myCounter有效地调用make()。在JavaScript中,可以将函数分配给变量,将其作为参数传递给其他函数,也可以嵌套在其他函数中,等等。

You'll also notice that the inner function make() is still able to access the value of counter variable defined in the outer function, even though the makeCounter() function has already finished executing (line no-14). It happens because functions in JavaScript form closures. Closures internally store references to their outer variables, and can access and update their values.

您还会注意到,即使makeCounter()函数已经完成执行(第14行),内部函数make()仍然能够访问在外部函数中定义的counter变量的值。发生这种情况是因为JavaScript中的函数形成了闭包。闭包内部存储对其外部变量的引用,并且可以访问和更新其值。

In the example above, the make() function is a closure whose code refers to the outer variable counter. This implies that whenever the make() function is invoked, the code inside it is able to access and update the counter variable because it is stored in the closure.

在上面的示例中,make()函数是一个闭包,其代码引用了外部变量counter。这意味着无论何时调用make()函数,由于其存储在闭包中,因此其内部的代码能够访问和更新counter变量。

Finally, since the outer function has finished executing, no other part of the code can access or manipulate the counter variable. Only the inner function has exclusive access to it.

最后,由于外部函数已完成执行,因此代码的其他部分都无法访问或操纵counter变量。仅内部函数对其具有独占访问权。

The previous example can also be written using anonymous function expression, like this:

前面的示例也可以使用匿名函数表达式编写,如下所示:

// Anonymous function expression
var myCounter = (function() {
var counter = 0; // Nested anonymous function
return function() {
counter += 1;
return counter;
}
})(); console.log(myCounter()); // Prints: 1
console.log(myCounter()); // Prints: 2

Tip: In JavaScript, all functions have access to the global scope, as well as the scope above them. As JavaScript supports nested functions, this typically means that the nested functions have access to any value declared in a higher scope including its parent function's scope.

提示:在JavaScript中,所有函数都可以访问全局范围以及它们上方的范围。由于JavaScript支持嵌套函数,因此通常意味着嵌套函数可以访问在较高范围(包括其父函数的范围)中声明的任何值。

Note: The global variables live as long as your application (i.e. your web page) lives. Whereas, the local variables have a short life span, they are created when the function is invoked, and destroyed as soon as the function is finished executing.

注意:全局变量的寿命与您的应用程序(即您的网页)的寿命一样长。局部变量的寿命很短,它们是在调用函数时创建的,并在函数执行完毕后立即销毁。

Creating the Getter and Setter Functions

Here we will create a variable secret and protect it from being directly manipulated from outside code using closure. We will also create getter and setter functions to get and set its value.

在这里,我们将创建一个变量secret,并防止它使用闭包直接从外部代码中进行操作。我们还将创建getter和setter函数以获取并设置其值。

Additionally, the setter function will also perform a quick check whether the specified value is a number or not, and if it is not it will not change the variable value.

另外,setter函数还将快速检查指定的值是否为数字,如果不是,则不会更改变量值。

var getValue, setValue;

// Self-executing function
(function() {
var secret = 0; // Getter function
getValue = function() {
return secret;
}; // Setter function
setValue = function(x) {
if(typeof x === "number") {
secret = x;
}
};
}()); // Calling the functions
console.log(getValue()); // Returns: 0
setValue(10);
console.log(getValue()); // Returns: 10
setValue(null);
console.log(getValue()); // Returns: 10

Tip: Self-executing functions are also called immediately invoked function expression (IIFE), immediately executed function, or self-executing anonymous function.

提示:自执行函数也称为立即调用函数表达式(IIFE),立即执行的函数或自我执行的匿名函数。

Lexical environment

let makeIncrement, makeDecrease;
function make() {
let count;
makeIncrement = function() {
return ++count;
};
makeDecrease = function() {
return --count;
}
count = 1;
console.log(`inside make, call to makeIncrement(): ${makeIncrement()}`);
}
make(); // 2
console.log(`call to makeDecrease(): ${makeDecrease()}`); // 1 (--count)
console.log(`call to makeDecrease(): ${makeDecrease()}`); // 0 (--count)
console.log(`call to makeIncrement(): ${makeIncrement()}`); // 1 (++count)
console.log(`call to makeIncrement(): ${makeIncrement()}`); // 2 (++count)

Reference to an unbound variable

// Reference to an unbound variable
let moduleSet = {
x: 42,
getX: function() {
return this.x;
}
}; let unboundGetX= moduleSet.getX;
console.log(unboundGetX());
// This function gets invoked at the gloal scope
// emits undefined as 'x' is not specified in global scope
// 函数在全局范围内被调用
// 发出undefined,因为未在全局范围内指定'x' // specify object moduleSet as the closure
// 指定对象moduleSet为闭包
let boundGetX = unboundGetX.bind(moduleSet);
console.log(boundGetX()); // emits 42

Understanding closures in depth的更多相关文章

  1. [大数据之Spark]——Actions算子操作入门实例

    Actions reduce(func) Aggregate the elements of the dataset using a function func (which takes two ar ...

  2. Spark官方文档 - 中文翻译

    Spark官方文档 - 中文翻译 Spark版本:1.6.0 转载请注明出处:http://www.cnblogs.com/BYRans/ 1 概述(Overview) 2 引入Spark(Linki ...

  3. android中如何实现离线缓存

    离线缓存就是在网络畅通的情况下将从服务器收到的数据保存到本地,当网络断开之后直接读取本地文件中的数据. 将网络数据保存到本地: 你可以自己写一个保存数据成本地文件的方法,保存在android系统的任意 ...

  4. Apache Spark 2.2.0 中文文档 - Spark 编程指南 | ApacheCN

    Spark 编程指南 概述 Spark 依赖 初始化 Spark 使用 Shell 弹性分布式数据集 (RDDs) 并行集合 外部 Datasets(数据集) RDD 操作 基础 传递 Functio ...

  5. JS -- The Scope Chain 作用域链

    The Scope Chain JavaScript is a lexically scoped language: the scope of a variable can be thought of ...

  6. Spark2.x详解

    一.概述 Apache Spark 是一个快速的, 多用途的集群计算系统. 它提供了 Java, Scala, Python 和 R 的高级 API,以及一个支持通用的执行图计算的优化过的引擎. 它还 ...

  7. spark RDD官网RDD编程指南

    http://spark.apache.org/docs/latest/rdd-programming-guide.html#using-the-shell Overview(概述) 在较高的层次上, ...

  8. 对Spark2.2.0文档的学习3-Spark Programming Guide

    Spark Programming Guide Link:http://spark.apache.org/docs/2.2.0/rdd-programming-guide.html 每个Spark A ...

  9. 转-Spark编程指南

    Spark 编程指南 概述 Spark 依赖 初始化 Spark 使用 Shell 弹性分布式数据集 (RDDs) 并行集合 外部 Datasets(数据集) RDD 操作 基础 传递 Functio ...

随机推荐

  1. [故障解决]图文:windows apache无法启用 端口被占用

    windows apache无法启用 端口被占用 1 XAMPP Error: Apache shutdown unexpectedly 无法启动apache,显示的log为: 2 查了一下端口, 通 ...

  2. P4720【模板】扩展卢卡斯,P2183 礼物

    扩展卢卡斯定理 最近光做模板了 想了解卢卡斯定理的去这里,那题也有我的题解 然而这题和卢卡斯定理并没有太大关系(雾 但是,首先要会的是中国剩余定理和exgcd 卢卡斯定理用于求\(n,m\)大,但模数 ...

  3. muduo网络库源码学习————日志滚动

    muduo库里面的实现日志滚动有两种条件,一种是日志文件大小达到预设值,另一种是时间到达超过当天.滚动日志类的文件是LogFile.cc ,LogFile.h 代码如下: LogFile.cc #in ...

  4. celery的定时任务

    定时任务 Celery 中启动定时任务有两种方式,(1)在配置文件中指定:(2)在程序中指定. # cele.py import celery app = celery.Celery('cele', ...

  5. 《Docker从入门到跑路》之多阶段构建

    多阶段构建就是在一个Dokcerfile中定义多个FROM,每个FROM都可以使用不同的基础镜像,并表示开始一个新的构建阶段,我们可以很方便的将一个阶段的文件复制到另外一个阶段中,在最终的阶段保存你需 ...

  6. c/c++获取硬盘序列号

    最近在接触软件注册模块,需要获取硬盘序列号来生成注册码. 硬盘序列号,英文名:Hard Disk Serial Number,该号是硬盘厂家为区别产品而设置的,是唯一的.网上搜索一下,发现获取硬盘序列 ...

  7. 初识Matlab及界面认识

    通过本章节的学习,需要掌握: MATLAB语言是什么 MATLAB在互联网语言中地位与应用 目标:利用MATLAB进行问题求解的基本规律.够使用MATLAB作为专业应用的工具. 1.什么叫计算? (1 ...

  8. java基础篇 之 再探内部类跟final

    之前写过一篇文章:从垃圾回收机制解析为什么局部内部类只能访问final修饰的局部变量以及为什么加final能解决问题,经过这两天的学习,发现有些不对,必须再来捋一捋 先看之前的例子: /** * @a ...

  9. NEON中的L可以避免溢出

    在做加法时,比如两个255x255的数值相加,那么正确结果将是130050,对一个最大值为65565的unsigned short是会溢出的,但是如果使用L命令时,则不会产生溢出.这说明L命令,不是先 ...

  10. jQuery中操作属性的方法attr与prop的区别

    attr 与 prop 都可以对某个属性进行获取和设置的操作,二者的用法相同: <script src = 'jQuery.js'></script> <script&g ...