闭包的示例
Closures are special thing in the JS world. It’s time to finally understand them.
在JS世界中,闭包是很特别的事情。 现在是时候终于了解它们了。
Many agree that closures are confusing at the beginning, but once you put some time into understanding them you will find them perhaps even intuitive.
许多人都同意闭包在开始时会造成混乱,但是一旦您花了一些时间来理解它们,您可能会发现它们甚至很直观。
There are lot’s of benefits coming from understanding them i.e.:
了解它们有很多好处,即:
- In opinion of many software leads, understanding closures is one of these things that separate the junior from the rest. 在许多软件主管看来,了解闭包是将初中生与其他人分开的事情之一。
There are patterns coming from functional programming i.e. partial application that make use of closures.
有一些来自函数式编程的模式,即使用闭包的部分应用程序 。
- if you are a React girl/guy, the React Hooks rely on closures 如果您是React女孩/家伙,那么React Hooks依赖于闭包
- It’s almost a certain question to be asked on JavaScript job interview. 在JavaScript求职面试中几乎要问一个问题。
- many other benefits coming from understanding closures 了解闭包还有很多其他好处
Yet I still see developers working for 5 and more years and ignoring the concept of closures — and they only refresh the knowledge or memorize the answers before a job interview.
但是,我仍然看到开发人员已经工作了5年以上,并且忽略了关闭的概念-他们只是在面试之前刷新知识或记住答案。
介绍 (Introduction)
Developers often learn best by tracking down the examples.
开发人员通常可以通过跟踪示例来最好地学习。
In this article we will:
在本文中,我们将:
- cover a bit of the theory regarding JavaScript closures (just enough to be productive and understand what are we writing) 涵盖有关JavaScript闭包的一些理论(足以提高生产力并了解我们在写什么)
- look into examples in more detail 更详细地研究示例
By the end of the article we will be able to understand what is a closure and how we can benefit from it in the JavaScript world.
到本文结尾,我们将能够了解什么是闭包以及如何在JavaScript世界中从中受益。
JavaScript中的闭包是什么 (What’s a closure in JavaScript)
In simple terms, closure gives us access to an outer function scope from an inner function. The closure in JavaScript has three scope chains:
简单来说,闭包使我们可以从内部函数访问外部函数范围。 JavaScript中的闭包具有三个作用域链:
- it has access to its own scope (between it’s own curly brackets) 它可以访问自己的范围(在自己的大括号之间)
- it has access to the outer function scope 它可以访问外部功能范围
- it has access to the global scope 它可以进入全球范围
In JavaScript, closures are created every time a function is created, at function creation time.
在JavaScript中, 每次创建函数时都会在函数创建时创建闭包。
一个简单的例子开始 (A simple example to begin with)
function outer() {
const b = 50;
function inner() {
const a = 100;
console.log(`a is ${a} and b is ${b}, the sum is ${a+b}`);
} return inner;
}// first invocation of outer
const fnFirst = outer();// second invocation of outer
const fnSecond = outer();// what's inside?
// note console.dir() and not console.log() commandconsole.dir(fnFirst);
console.dir(fnSecond);
To understand what is closure let’s walk through this example.
要了解什么是闭包,让我们来看一下这个示例。
第一次调用函数external() (Function outer() invoked for the first time)
We create variable
b
. The scope of variableb
is limited to theouter
function. The value is set to100
. To keep things more readable, I’ve initialised it withconst
. This means, that the value cannot be reassigned.我们创建变量
b
。 变量b
的范围限于outer
函数。 该值设置为100
。 为了使内容更具可读性,我已使用const
对其进行了初始化。 这意味着不能重新分配该值。Then we declare function
inner
— nothing is executed.然后,我们将函数声明为
inner
-不执行任何操作。Next, we find
return inner
. It turns out thatinner
is afunction
and as such we return the function body.接下来,我们找到
return inner
。 事实证明,inner
是一个function
,因此我们返回了函数主体。Please note that the statement
请注意,声明
return inner
does not execute functioninner
. Function is executed only when followed by()
i.e.inner()
.return inner
不执行函数inner
。 仅在后跟()
即inner()
时才执行函数。The content returned by
return inner
is stored intofnFirst
variable.return inner
返回的内容存储在fnFirst
变量中。In this case, it’s the body of function
在这种情况下,它是功能的主体
inner
.inner
。Function
outer()
finishes execution.函数
outer()
完成执行。All variables within the scope of
范围内的所有变量
outer()
no longer exist.outer()
不再存在。This is very important to understand, so I will repeat that. :)
了解这一点非常重要,因此我将重复一遍。 :)
Once a function completes its execution, any variables defined inside the function scope no longer exist.
一旦函数完成执行,在函数范围内定义的任何变量将不复存在。
This means that our variable b
declared inside outer
function, exists only when outer
function is being executed.
这意味着我们在outer
函数内部声明的变量b
仅在执行outer
函数时存在。
When we execute the function for the second time, the variables of the function are created again (from scratch).
当我们第二次执行该函数时,将再次(从头开始)创建该函数的变量。
Let’s track down what happens when we invoke function outer
for the second time (and store it into different variable).
让我们追踪一下第二次调用outer
函数(并将其存储到其他变量中)时发生的情况。
第二次调用函数external() (Function outer() invoked for the second time)
We create variable
b
. The scope of variableb
is limited to theouter
function. The value is set to100
.我们创建变量
b
。 变量b
的范围限于outer
函数。 该值设置为100
。Then we declare function
inner
so nothing is executed.然后,我们将函数声明为
inner
因此不执行任何操作。Similarly,
return inner
return the body ofinner
— remember the function is not called.同样,
return inner
收益身体inner
-记函数没有被调用。The contents returned by the return statement are stored in
fnSecond
.return语句返回的内容存储在
fnSecond
。Function
outer()
finishes execution.函数
outer()
完成执行。All variables within the scope of
范围内的所有变量
outer()
no longer exist.outer()
不再存在。
Here comes the important bit (once again but it’s worth repeating).
重要的地方到了(再次,但是值得重复)。
When the outer()
function is invoked for the second time, the variable b
is created from fresh. The variable b
cease to exist once outer
finishes executing.
当第二次调用external outer()
函数时,将从新创建变量b
。 一旦outer
完成执行,变量b
不再存在。
使开发人员感到困惑的部分 (The part that is confusing the devs)
So far it looks rather clear.
到目前为止,看起来还很清楚。
We have returned our function into two different variables fnFirst
and fnSecond
. These two variables are in fact functions, which we can verify with help of typeof
. The variables fnFirst
and fnSecond
both have the function body of inner
我们已将函数返回到两个不同的变量fnFirst
和fnSecond
。 这两个变量实际上是函数,我们可以在typeof
帮助下进行验证。 变量fnFirst
和fnSecond
都具有inner
函数体
console.log(fnFirst);// outputƒ inner() {
const a = 100;
console.log(`a is ${a} and b is ${b}, the sum is ${a + b}`);
}
Here comes the tricky part
棘手的部分到了
The console.log
wants to print variable b
.
console.log
要打印变量b
。
How we will be able to access the variable b
which was declared in the scope of function outer
?
我们将如何访问在函数outer
范围内声明的变量b
?
An example to help us:
一个可以帮助我们的例子:
// continuedconst fnFirst = outer();
const fnSecond = outer();// for the first time
// output -> a is 100 and b is 50, the sum is 150
fnFirst();// for the second time
// output -> a is 100 and b is 50, the sum is 150
fnFirst();// for the third time
// output -> a is 100 and b is 50, the sum is 150
fnFirst();// for the first time
// output -> a is 100 and b is 50, the sum is 150
fnSecond();
The following happens on the invocation of fnFirst()
.
调用fnFirst()
时发生以下情况。
Variable
a
is created, it’s value is set to100
.创建变量
a
,其值设置为100
。In the second line, the function tries to add
a + b
.在第二行中,该函数尝试添加
a + b
。Variable
变量
a
has just been created, that part is clear.a
刚刚创建,那部分很清楚。But there is no
但是没有
b
in the function body.b
在功能主体中。We’ve said earlier, that the
我们之前说过,
b
variable exists only when functionouter
is executed.b
变量仅在执行outer
函数时存在。
We still don’t know how does inner
function knows about the variable b
.
我们仍然不知道inner
函数如何知道变量b
。
营救结束 (Closures to the rescue)
If we would do console.dir(fnFirst)
, we would see something similar in the console:
如果我们执行console.dir(fnFirst)
,我们将在控制台中看到类似的内容:
We can see that there is a Closure that has variable b = 50
我们可以看到一个闭包具有变量b = 50
That’s our answer!
那就是我们的答案!
The inner
function has preserved the value of variable b
and continues to closure it.
inner
函数保留了变量b
的值并继续对其进行封闭。
The
inner
function preserves the scope chain of the enclosing function at the time the enclosing function was executed. In simple terms, this means thatinner
function can accessouter
function variables with help from closure.
inner
函数在执行封闭函数时保留了封闭函数的作用域链。 简单来说,这意味着inner
函数可以在闭包的帮助下访问outer
函数变量。
As we have said in the beginning. The inner function has access to three scope chains:
正如我们在一开始所说的。 内部函数可以访问三个作用域链:
the inner scope i.e.
a
内范围即
a
the outer scope i.e.
b
外部范围即
b
- the global scope 全球范围
In JavaScript, closures are created every time a function is created, at function creation time.
在JavaScript中,每次创建函数时都会在函数创建时创建闭包。
更困难的例子 (More difficult example)
In the job interview you will be probably be given a code snippet similar to that one:
在工作面试中,您可能会收到与该代码段相似的代码段:
Then the interviewer would probably ask you the following questions:
然后,面试官可能会问您以下问题:
- tell me what this snippet is? (we already know it’s closure) 告诉我这个片段是什么? (我们已经知道它是关闭的)
- walk me through what’s happening? (most important part) 引导我了解发生了什么事? (最重要的部分)
- what will be the output in the console? 控制台中的输出是什么?
To answer that questions, let’s walk through what happens when we invoke fnFirst
for the first time (let’s remember that we are looking at inner
function as this is what has been returned):
为了回答这些问题,让我们fnFirst
一下第一次调用fnFirst
时会发生什么(请记住,我们正在查看inner
函数,因为这是返回的结果):
fnFirst首次调用 (fnFirst invoked for the first time)
Variable
c
is created and initialised as20
.创建变量
c
并将其初始化为20
。Message is logged into the console with
a,b,c
variables.消息将使用
a,b,c
变量登录到控制台。We already know that variables
我们已经知道变量
a
andb
are available and come from the closure.a
和b
都可用,并且来自于闭包。We can think of it like
我们可以这样想
a(first_time)
andb(first_time)
.a(first_time)
和b(first_time)
。Hence
因此
a = 10
andb = 100
.a = 10
和b = 100
。The variable
b
, coming from the closure andc
variable coming frominner
function are incremented.来自闭包的变量
b
和来自inner
函数的c
变量递增。When the function
fnFirst
completes execution, the variables inside it (c
) cease to exist.当函数
fnFirst
完成执行时,函数(c
)中的变量将不复存在。However, the value
但是,价值
b
was preserved to the closure and as such is preserved to the next function call.b
保留在闭包中 ,因此保留在下一个函数调用中 。
在第二,第三和…时间调用fnFirst() (fnFirst() invoked for the second, third, and … time)
It’s similar as the first invocation, the only difference is step 2 — the value of b
variable is already incremented.
它与第一次调用类似,唯一的区别是步骤2 – b
变量的值已经增加。
The variable
c
is created and initiated as20
创建变量
c
并将其初始化为20
The variable
b
, coming from the closure, is incremented by1
hence it’s value is101
. In next function call it will be102
, then103
and so on.来自闭包的变量
b
增加1
因此其值为101
。 在下一个函数调用中,它将是102
,然后是103
,依此类推。b
variable, coming from the closure andc
variable coming frominner
function are yet again incremented.来自闭包的
b
变量和来自inner
函数的c
变量再次递增。When the function
fnFirst
completes execution, the variables inside it ( i.e.c
) cease to exist. However, the valueb
was preserved to the closure and as such is preserved to the next function call.当函数
fnFirst
完成执行时,其中的变量(即c
)将不复存在。 但是,值b
保留给闭包,因此保留给下一个函数调用。
You can also use closures for a pattern called partial application but that is a story for another article.
您也可以将闭包用于称为部分应用程序的模式,但这是另一篇文章的故事。
摘要 (Summary)
Closures are initially difficult to understand especially when introduced with advanced jargon definitions.
闭包最初很难理解,尤其是在引入高级术语定义时。
If that’s the case, we can try to ignore the jargon initially, go through some examples, understand them, and then come back to the jargon.
如果是这种情况,我们可以尝试先忽略术语,通过一些示例,理解它们,然后再回到术语。
With a bit of practice you will probably come to realise that closure is in fact intuitive, powerful tool for us.
通过一些实践,您可能会发现封闭实际上对我们来说是直观,功能强大的工具。
PS
聚苯乙烯
There is no escape from closure s— you still have to understand it on a job interview (or at an exam).
结束考试无可避免,您仍然必须在求职面试(或考试)中了解它。
闭包的示例