JavaScript的深空——匿名函数和闭包

举报
yd_221104950 发表于 2020/12/02 23:42:55 2020/12/02
【摘要】 所谓匿名函数指的就是没有名称的函数。使用函数声明时,必须给它指定名称,但使用函数表达式时,则不必给它指定名称。所以匿名函数就是没有名称的函数表达式。函数表达式的结果是一个引用。函数是一等值,后续会提到。 嵌套对作用域的影响 我们来看一个嵌套的函数: <!doctype html> <html lang="en"> <head>&...

所谓匿名函数指的就是没有名称的函数。使用函数声明时,必须给它指定名称,但使用函数表达式时,则不必给它指定名称。所以匿名函数就是没有名称的函数表达式。函数表达式的结果是一个引用。函数是一等值,后续会提到。

嵌套对作用域的影响

我们来看一个嵌套的函数:

<!doctype html>
<html lang="en"> <head><title> hello world!</title> </head> <body> <script> var  test = function(){ var hello = "Good afternoon"; function inner(){ return hello; } alert(inner()); } test(); </script> </body>
</html>

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18

在代码顶层定义的东西都是全局的,如test是全局变量。而inner函数是嵌套在函数test中的,所以它的作用域是整个test函数内部,而对外是不可见的。

将函数作为实参传给另一个函数时,传入的函数引用将被复制到所调用的一个形参变量中,与其他形参一样,存储函数引用的形参也是局部变量。

词法作用域

所谓的词法作用域,指的是JavaScript的作用域规则完全基于代码的结构,而不是一些动态的运行阶段属性。这意味着只需查看代码的结构,就能确定变量是在什么地方定义的。如:

<!doctype html>
<html lang="en"> <head><title> hello world!</title> </head> <body> <script> var hello = "Good 888888"; function test(){ var hello = "Good afternoon8"; function inner(){ return hello; } return inner; } var inner_func = test(); alert(inner_func()); </script> </body>
</html>

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19

inner函数返回的是 “Good afternoon8”,根据词法作用域,我们只需要从函数内往外查找,即在最近的函数作用域内开始查找hello变量,如果没有找到才会到全局作用域内查找。

词法作用域指出,最重要的是函数是什么地方定义的,从它定义的地方开始往外逐层查找。如上例,首先在inner函数内的作用域查找hello变量,如果找不到,再到外一层(即test函数作用域)去查找。

怪异的情况 :每当inner被调用时,hello这个局部变量都被认为是还存在的,需要时就可直接使用,为什么可以这样呢?这,我们就要从浏览器的JavaScript的解析器怎么分析我们的test函数说起了,
让我们看一下到底发生了什么事。

执行test函数时发生了什么事情?
(1)我们首先遇到了局部变量hello ,然后创建了这个变量,并将字符串“Good afternoon8”赋给了它。
(2)紧接着,所有的局部变量都存储在一个环境(environment)中,这个环境(environment)存储了在局部作用域内定义的所有变量,在此实例中只有hello一个局部变量被存储在环境中。
(3)接下来创建了inner函数
(4)最后,当我们返回inner这个函数时,返回的不仅仅是函数的引用,还有与之相关的环境的引用

这也就解释了上述示例中为什么调用 inner_func函数时,hellp变量的值还存在的问题。

另外,在JavaScript中,只有函数会引入新的作用域。因此,对于在函数中引用的变量 ,要确定它是在哪里定义的,可从最里面(当前函数)开始依次向最外面进行查找,直到找到它为止。如果在函数中都找不到它,则它要么是全局的,要么是未定义的。值得注意的一点是形参也被视为函数的局部变量 ,因此它们也会被包含在环境(environment)中

闭包!闭包!闭包!

闭包(closure)指的是函数和引用环境(就是存储了在局部作用域内定义的所有变量的环境)。 我们通过以下例子来介绍:

<!doctype html>
<html lang="en"> <head><title> hello world!</title> </head> <body> <script> var hello = "Good 888888"; function test(){ var hello = "Good afternoon8"; function inner(){ var name = "Tom"; return hello + " " +name; } return inner; } var inner_func = test(); alert(inner_func()); </script> </body>
</html>

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20

函数通常包含局部变量(它们都是在函数体中定义的,包括所有的形参),还可能包含不是在本地定义的变量 ,如inner函数里的hello变量就不是在inner函数体中定义的),而像hello这些不是在本地定义的,又不是全局变量,而是定义在包含当前函数的外层函数中的变量,被称为自由变量 。

所以,对于在函数体内的变量 ,如果它既不是在本地定义,又不是全局变量的话,那么它肯定来自包含当前函数的其他函数,这种变量称为自由变量。自由变量会被存储在环境中。如上例的inner函数中的hello变量就是自由变量,因为它既不是在本地定义,又不是全局变量,那么它一定来自包含inner函数的test函数中。所以hello变量是自由变量,一定会被保存到环境中去。

最后再说一遍什么是闭包!!!

包含自由变量的函数(如上例的inner函数)与为所有这些自由变量提供了变量绑定的环境一起,称为闭包。

闭包的使用

如我们通过对比来看看闭包的使用与优势:
普通计数器的实现

<html lang="en"> <head><title> hello world!</title> </head> <body> <script> var count = 0; function counter(){ return ++count; } var c = counter; alert(c()); alert(c()); alert(c()); </script> </body>
</html>

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17

上面这种普通的做法是引入了全局变量count,但是缺点也在这里,因为协作开发代码时,大家常常会使用相同的变量名,从而容易导致冲突。我们其实可以使用受保护的局部变量来实现计数,这样就不会与任何代码发生冲突,且只能通过调用相应的函数(也叫闭包)来增加计数器的值。如下例。

闭包实现计数器

<html lang="en"> <head><title> hello world!</title> </head> <body> <script> function makeCounter(){ var count = 0; function counter(){ return ++count; } return counter; } var c = makeCounter(); alert(c()); alert(c()); alert(c()); </script> </body>
</html>

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22

函数闭包的创建

(1)通过从函数返回函数来创建闭包,如上面的例子。
哈哈哈!你以为只能通过从函数返回函数来创建闭包吗?其实如果函数使用了自由变量,则每当你在创建该函数的上下文外面执行它时,都将创建一个闭包。

(2)将函数传递给函数时,也将创建闭包。
在这种情况下,传递的函数将在完全不同于定义它的上下文 中执行,如:

<!doctype html>
<html> <head><title>Hello world</title> </head> <body> <script> function makeTimer(message,n){ setTimeout(function(){//定义了一个函数 alert(message); //它使用了一个自由变量 },n); } makeTimer("hello world!!!",5000); </script> </body>
</html>

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16

这里向函数setTimeout传入了一个函数表达式,而这个函数表达式使用了自由变量message,因为函数表达式返回的是函数的引用,而该引用被传递给了函数setTimeout,setTimeout函数存储了该函数的引用(这是一个函数及其环境,即闭包),将在5秒后调用它。
传递给setTimeout的函数之所以是一个闭包,是因为它带有将自由变量message绑定到字符串“hello world!!!”的环境。

特别注意:**闭包的环境引用的是实时变量 ,即如果闭包函数外面的代码修改了变量,闭包函数执行时看到的将是变量的新值,**而不是所有变量及其值的副本闭包包含的是实际环境,而非环境的副本。如:

<!doctype html>
<html> <head><title>Hello world</title> </head> <body> <script> function makeTimer(message,n){ setTimeout(function(){ alert(message); },n); message = "bye bye!";  //在闭包函数外面的代码修改了变量message的值,闭包函数执行时看到的将是变量的新值 } makeTimer("hello world!!!",5000); </script> </body>
</html>

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17

使用事件处理程序来创建闭包,这是很常见的一种方法

<!doctype html>
<html lang="en"> <head><title> event </title> </head> <body> <script> window.onload = init; function init(){ var count = 0; var message = "the times that you have clicked:"; var button = document.getElementById("btn"); var mydiv = document.getElementById("mydiv"); button.onclick = function(){ ++count; mydiv.innerHTML = message + count; } } </script> <button id="btn">click me!</button> <div id="mydiv">You have clicked 0 time</div> </body>
</html>

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22

我们通过将一个函数表达式赋给按钮的属性onclick来指定单击处理程序。因此可以在这个单击处理程序中引用mydiv、count、message三个自由变量,环境中就包含了这三个变量及其绑定的值,也因此将创建一个闭包。也就是说把一个闭包赋给按钮的属性onclick。

看看浏览器会在怎样的情况下会创建闭包

当浏览器看到函数中引用了自由变量,它就会创建闭包,也就是会在环境中保存所有局部变量的值,包括形参,并返回函数的引用和环境的引用。

小结一下:

  1. 匿名函数是没有名称的函数表达式。
  2. 可将函数表达式传递给函数,还可以从函数返回函数表达式
  3. 函数表达式的结果是一个函数引用,因此在可以使用函数引用的任何地方都可以使用函数表达式。
  4. 嵌套函数是在其他函数中定义的函数。
  5. 与局部变量一样,嵌套函数的作用域也是局部的。
  6. 词法作用域意味着看代码结构就能知道其作用域。
  7. 闭包指的是函数及其引用的环境。
  8. 闭包捕获其所创建时所处作用域的变量的值。
  9. 自由变量指的是在函数体内未绑定的变量。
  10. 在创建闭包的上下文外部执行它时,自由变量的值由引用的环境决定的。
  11. 通常使用闭包来为事件处理程序捕获状态。

谢谢阅读。

文章来源: blog.csdn.net,作者:WongKyunban,版权归原作者所有,如需转载,请联系作者。

原文链接:blog.csdn.net/weixin_40763897/article/details/88037394

【版权声明】本文为华为云社区用户转载文章,如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。