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>

在代码顶层定义的东西都是全局的,如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>

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>

函数通常包含局部变量(它们都是在函数体中定义的,包括所有的形参),还可能包含不是在本地定义的变量 ,如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>

上面这种普通的做法是引入了全局变量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)将函数传递给函数时,也将创建闭包。
在这种情况下,传递的函数将在完全不同于定义它的上下文 中执行,如:

<!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>

这里向函数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>

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

<!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>

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

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

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

小结一下:

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

谢谢阅读。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值