前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >解释JavaScript中的闭包

解释JavaScript中的闭包

作者头像
lesM10
发布2019-08-26 16:35:33
8980
发布2019-08-26 16:35:33
举报

去年我写了一篇“closures的简介”,它的目的是帮助大家理解‘什么是闭包,闭包是如何工作的’。现在我尝试从另外一个不同的角度去阐释闭包。有了这些基本的概念,你只需要尽可能多地阅读这些解释,来更全面地理解闭包。

First-class functions

就像我在“Why JavaScript is AWESOME”中解释的那样,JavaScript的强大之处的一部分来自于它的’first-class functions‘。那么编程语言中的’first-class‘意味着什么?

可以被存放在变量和数据结构中 可以作为子例程的参数被传递 可以作为子例程的返回值被返回 可以在运行时被构造 有固有的id(区别于任何给定的名字)

所以,JavaScript中的functions非常类似objects。事实上,在JavaScript中functions就是objects。能够嵌套使用函数,让我们可以使用闭包,这也是我接下来要讨论的...

Nested functions(嵌套函数)

如下是嵌套函数的一个小例子:

http://jsfiddle.net/skilldrick/66jFm/embedded/

在这儿要注意的重要事情是只有唯一的一个f函数被定义。每次函数f被调用后,一个新的函数g被创建,(函数g)局部于函数f的执行过程中。当函数g被返回时,我们可以把它赋值给一个全局变量。所以,我们可以调用f函数,把结果赋值给变量g5;接着我们再一次调用f函数,并把结果赋值给变量g1。g1和g5是2个不同的函数,但碰巧的是它们共享着同一份代码,只不过它们他们在不同的上下文中被执行,使用着不同的‘free variable(自由变量)’。(题外话,使用函数定义去定义‘函数g’,接着返回函数g,并不是必需的。我们可以使用函数表达式作为替代,函数表达式允许我们创建函数时不用命名函数。也就是直接返回匿名函数。这儿是使用匿名函数替换后的版本)

Free variables and scope(自由变量和作用域)

如果一个变量在包含它的作用域中被定义,那么该变量在包含它的作用域内的任何其它作用域内都是自由的(原文:A variable is free in any particular scope if it is defined within an enclosing scope.)。为了表达的更具体,也就是:在函数g的作用域内,变量x是自由的。因为变量x是在函数f的作用域内被定义的(而且‘作用域f’包含‘作用域g’)。同样地,任何全局变量在‘作用域f’和’作用域g‘内也是自由的。

作用域意味着什么?一个作用域是一个代码区,在该代码区中可以定义变量,并且包围该作用域的外围作用域不能访问该作用域内的变量(原文:A scope is an area of code where a variable may be defined, without the enclosing scope knowing about it.)。JavaScript有‘函数作用域’,所以函数有它自己的作用域。所以在‘函数f’中定义的任何变量,外部都是看不到的。作用域是可以嵌套的,所以,在上述例子中,函数g有它自己的作用域,函数g的作用域被函数f包围着,函数f的作用域被全局作用域包围着。当一个变量被访问时,JavaScript解释器在当前作用域内查找变量,如果在当前作用域内找不到该变量的定义,解释器会查看包围着当前作用域的作用域,接着是查看爷爷作用域,一直向上直到全局作用域。如果此时,变量的定义仍未被找到,一个ReferenceError异常就会被抛出。

Closures are functions that retain a reference to their free variables(闭包是‘保留着它们的自由变量的一份引用’的函数)

并且这是问题的本质。让我们先看下上述例子的一个简化版本:

http://jsfiddle.net/skilldrick/XDrsn/embedded/

调用函数f时传递一个参数5,执行函数f时,函数g会被调用。当函数g被调用时,函数g可以访问那个形参x,这并没有什么奇怪的。令人惊讶的地方在于,当你从函数f中返回函数g后,返回的函数g在被调用时仍然可以访问你传递的参数5(就像原先那个例子中展示的那样)。让人迷惑的地方在于:函数g被返回后,仍然记得在函数f被调用时被定义的变量x(这也是大家理解闭包时,有困惑的地方)。从这点来说,确实不能理解。那么看看下面的例子:

http://jsfiddle.net/skilldrick/DEUdF/embedded/

当person被调用,形参name一定是被传递的值。所以,person第一次被调用时,name一定是‘Dave’,person第二次被调用,name一定是‘Mary’。person定义了2个内部函数‘set和get’。当这些函数(set和get)第一次被定义时,它们有一个‘自由变量name’,并且name的值一定是’Dave‘。这2个函数被数组包裹着返回,在外部被取出并赋值给2个变量’getDave和setDave‘。(如果你想从函数中返回一个以上的值,你要么返回一个对象,要么返回一个数组。在这里使用数组显得有点啰嗦,但是如果使用对象的话会混淆我们讨论的问题。这儿又一个使用对象的版本,如果它对你来说更好理解的话)

并且这就是神奇的地方,getDave和setDave都记得同一个变量name(name初始时一定是Dave)。当setDave(’Bob‘)被调用时,变量name被设置为’Bob‘。现在getDave被调用,它返回了’Bob‘。所以getDave和setDave这两个函数记得同一个变量。这也就是我想表达的含义:’闭包是保留它们自由变量的一份引用的函数‘。getDave和setDave都记得它们共有的自由变量name。即使person已经返回,但是变量name继续存活,因为变量name被getDave和setDave引用着。

当person第一次被调用时,变量name一定是’Dave‘。当person第二次被调用时,变量name的一份新版本被创建,当然get和set也被新建了一份。所以getMary,setMary函数和getDave,setDave是完全不同的。虽然它们执行着同样的代码,但是它们的上下文环境不同,有着不同的自由变量。

Summary总结

总的来说,闭包是一个函数’该函数在一个上下文中被调用,(该函数)却记得在另一个上下文中定义的变量‘(也就是该函数被定义的上下文)。在同一个上下文中定义的多个闭包记得同样的上下文,所以任何一个闭包修改上下文,其他闭包也会受影响(因为多个闭包共享同一个上下文,就像上面例子显示的那样 setDave('Bob')后 getDave()也会受到影响)。 本文翻译自:http://skilldrick.co.uk/2011/04/closures-explained-with-javascript/ 转载请注明出处

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2017.06.09 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • First-class functions
  • Nested functions(嵌套函数)
  • Free variables and scope(自由变量和作用域)
  • Closures are functions that retain a reference to their free variables(闭包是‘保留着它们的自由变量的一份引用’的函数)
  • Summary总结
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档