Understanding closures in depth
什么是闭包
维基百科中的概念
- 在计算机科学中,闭包(英语: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
letkeyword.在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
countervariable can be changed from anywhere in the program, without calling themakeCounter()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
countervariable cannot be manipulated from outside, since it is local tomakeCounter()function, but its value will also not increase after subsequent function call, because every time we call the function it reset thecountervariable 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 functionmakeCounter(). So the value of themyCounteris the innermake()function (line no-14), and callingmyCountereffectively callsmake(). 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 ofcountervariable defined in the outer function, even though themakeCounter()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 variablecounter. This implies that whenever themake()function is invoked, the code inside it is able to access and update thecountervariable 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
countervariable. 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
secretand 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的更多相关文章
- [大数据之Spark]——Actions算子操作入门实例
Actions reduce(func) Aggregate the elements of the dataset using a function func (which takes two ar ...
- Spark官方文档 - 中文翻译
Spark官方文档 - 中文翻译 Spark版本:1.6.0 转载请注明出处:http://www.cnblogs.com/BYRans/ 1 概述(Overview) 2 引入Spark(Linki ...
- android中如何实现离线缓存
离线缓存就是在网络畅通的情况下将从服务器收到的数据保存到本地,当网络断开之后直接读取本地文件中的数据. 将网络数据保存到本地: 你可以自己写一个保存数据成本地文件的方法,保存在android系统的任意 ...
- Apache Spark 2.2.0 中文文档 - Spark 编程指南 | ApacheCN
Spark 编程指南 概述 Spark 依赖 初始化 Spark 使用 Shell 弹性分布式数据集 (RDDs) 并行集合 外部 Datasets(数据集) RDD 操作 基础 传递 Functio ...
- JS -- The Scope Chain 作用域链
The Scope Chain JavaScript is a lexically scoped language: the scope of a variable can be thought of ...
- Spark2.x详解
一.概述 Apache Spark 是一个快速的, 多用途的集群计算系统. 它提供了 Java, Scala, Python 和 R 的高级 API,以及一个支持通用的执行图计算的优化过的引擎. 它还 ...
- spark RDD官网RDD编程指南
http://spark.apache.org/docs/latest/rdd-programming-guide.html#using-the-shell Overview(概述) 在较高的层次上, ...
- 对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 ...
- 转-Spark编程指南
Spark 编程指南 概述 Spark 依赖 初始化 Spark 使用 Shell 弹性分布式数据集 (RDDs) 并行集合 外部 Datasets(数据集) RDD 操作 基础 传递 Functio ...
随机推荐
- 移动App性能测评与优化1.4.4 多进程应用
1.4.4 多进程应用 根据上一节中的描述,当一个进程结束后,它所占用的共享库内存将会被其他仍然使用该共享库的进程所分担,共享库消耗的物理内存并不会减少.实际上,对于所有共享使用了这个库的应用,Pss ...
- 如何将MAC的 Terminal 行首变得清爽简洁一点?
作为一位开发人员,MAC带给我们更好的编程体验,Terminal也是经常会去操作的东西,但是说实话,默认的 Terminal 的各种设置,真的让我好难受 刚开始打开,可能看到的会是这样的,行首一大堆东 ...
- 2) 接口规范 原生django接口、单查群查 postman工具 CBV源码解析
内容了解 """ .接口:什么是接口.restful接口规范 .CBV生命周期源码 - 基于restful规范下的CBV接口 .请求组件.解析组件.响应组件 .序列化组件 ...
- 题目分享C 二代目
题意:一个数列是由 1 1 2 1 2 3 1 2 3 4 1 2 3 4 5 1 2 3 4 5 6.....组成,也就是1-1,1-2,1-3......并且如果遇到多位数也要拆成数字比如1-10 ...
- VUE生命周期中的钩子函数及父子组件的执行顺序
先附一张官网上的vue实例的生命周期图,每个Vue实例在被创建的时候都需要经过一系列的初始化过程,例如需要设置数据监听,编译模板,将实例挂载到DOM并在数据变化时更新DOM等.同时在这个过程中也会运行 ...
- 数据源管理 | 基于DataX组件,同步数据和源码分析
本文源码:GitHub·点这里 || GitEE·点这里 一.DataX工具简介 1.设计理念 DataX是一个异构数据源离线同步工具,致力于实现包括关系型数据库(MySQL.Oracle等).HDF ...
- 戴尔服务器ipmi报错
戴尔服务器ipmi配置完成,用浏览器打开报错 查看器已终止,网络已中断 原因:这个问题是java报错,用火狐打开报错 解决方法: 用IE打开就没问题,IE要用较高版本的,楼主的是win10-IE11
- 1、Hbase原理分析
一.Hbase介绍 1.1.对Hbase的认识 HBase作为面向列的数据库运行在HDFS之上,HDFS缺乏随机读写操作,HBase正是为此而出现. HBase参考 Google 的 Bigtable ...
- 中文分词工具简介与安装教程(jieba、nlpir、hanlp、pkuseg、foolnltk、snownlp、thulac)
2.1 jieba 2.1.1 jieba简介 Jieba中文含义结巴,jieba库是目前做的最好的python分词组件.首先它的安装十分便捷,只需要使用pip安装:其次,它不需要另外下载其它的数据包 ...
- thread模块—Python多线程编程
Thread 模块 *注:在实际使用过程中不建议使用 thread 进行多线程编程,本文档只为学习(或熟悉)多线程使用. Thread 模块除了派生线程外,还提供了基本的同步数据结构,称为锁对象(lo ...