JavaScript的深空——匿名函数和闭包
所谓匿名函数指的就是没有名称的函数。使用函数声明时,必须给它指定名称,但使用函数表达式时,则不必给它指定名称。所以匿名函数就是没有名称的函数表达式。函数表达式的结果是一个引用。函数是一等值,后续会提到。
嵌套对作用域的影响
我们来看一个嵌套的函数:
<!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。
看看浏览器会在怎样的情况下会创建闭包
当浏览器看到函数中引用了自由变量,它就会创建闭包,也就是会在环境中保存所有局部变量的值,包括形参,并返回函数的引用和环境的引用。
小结一下:
- 匿名函数是没有名称的函数表达式。
- 可将函数表达式传递给函数,还可以从函数返回函数表达式
- 函数表达式的结果是一个函数引用,因此在可以使用函数引用的任何地方都可以使用函数表达式。
- 嵌套函数是在其他函数中定义的函数。
- 与局部变量一样,嵌套函数的作用域也是局部的。
- 词法作用域意味着看代码结构就能知道其作用域。
- 闭包指的是函数及其引用的环境。
- 闭包捕获其所创建时所处作用域的变量的值。
- 自由变量指的是在函数体内未绑定的变量。
- 在创建闭包的上下文外部执行它时,自由变量的值由引用的环境决定的。
- 通常使用闭包来为事件处理程序捕获状态。
谢谢阅读。
文章来源: blog.csdn.net,作者:WongKyunban,版权归原作者所有,如需转载,请联系作者。
原文链接:blog.csdn.net/weixin_40763897/article/details/88037394
- 点赞
- 收藏
- 关注作者
评论(0)