zoukankan      html  css  js  c++  java
  • 学习笔记=>《你不知道的JavaScript(上卷)》第五章:作用域闭包

    什么是词法作用域?

      在之前讲过,我们平常写代码的时候,创建一个变量和方法的时候在其书写的位置(所在环境)会形

      成一个作用域,即为词法作用域,该作用域中的属性和方法只能在当前环境内使用。

    闭包

      最简单的一个闭包实例:

    function fun(){
          var a = 2;  
          function bar(){
                 return a;
          }
          return bar;
    }
    
    var baz = fun();
    console.log(baz());      //2

      上面例子中定义了一个函数fun,函数内有声明了一个变量a和函数bar,代用fun的时候return出函数bar。

      由于作用域的规则,函数bar内可以使用变量a,当调用函数fun时,return出函数bar并赋值给baz,这个

      时候实际上baz引用了内部函数bar,也在外边访问到了函数fun内的变量a,形成闭包。闭包的一大特性

      就是将函数内部变量通过闭包让内部函数在其词法作用域外被执行,这个时候有用bar被baz引用,而变量

      a又被bar引用,所以浏览器回收机制不会回收fun中的作用域。

      其他闭包场景:

    //将函数内部嵌套函数作为参数传给外部函数
    function foo(){
          var a = 2;
          function bar(){
                return a++;
          }
          //形成闭包
          baz(bar);
    }
    
    function baz(fn){
           console.log(fn(),fn(),fn());    //2,3,4
    }
    foo();
    
    
    //间接传递函数
    var fn;
    
    function foo(){
           var a = 2;
           function bar(){
                 return a;
           }
           //将内部函数保存到全局作用域,间接传递内部函数,形成闭包
           fn = bar;
    }
    
    function baz(){
           console.log(fn());
    }
    
    foo();
    baz();    //2
    baz();    //3
    baz();    //4

      setTimeout中的闭包:

    function fn(msg){
          window.setTimeout(function timer(){
                 console.log(msg);
          },1000);
    }
    
    fn('hello world');

      解析上面中的例子:将一个内部函数(这里是timer)传递给setTimeout作为第一个参数,根据词法作用域,timer函

      数具有涵盖函数fn作用域的闭包,so,timer保有对变量msg的一引用。

      1s后函数timer执行时,fn内的作用域依然不会消失(回收),timer依然保有fn作用域的闭包。

    深入引擎内部原理,内置的工具函数setTimeout,持有对一个参数的引用,这个参数可以叫任何名字,引擎会调用这个函数,在上面的例子中为timer,而setTimeout所在词法作用域在这个过程中保持完整(回调执行过程中劫持了工具函数所在词法作用域,形成闭包,保证工具函数回调执行时可以访问到词法作用域中变量)。

    循环和闭包:

      要说明闭包,for循环时最常见的例子:

    for(var i=0;i<3;i++){
           //回调会在for循环完毕后才会执行
           window.setTimeout(function(){
                  console.log(i);    //3,3,3
           },0);
    }    

      我们试图假设每次循环中,每个迭代在运行时都会给自己‘捕获’一个i的副本,但是根据作用域工作原理,尽管每次迭代都会分别定义一个函数,但是他们都封闭在一个共享的全局作用域里面,因此只有一个i。

      使用闭包在每次迭代的时候创建一个封闭的作用域:

    for(var i=0;i<3;i++){
           //每次迭代创建一个作用域
           (function(){
                  //劫持对当前循环中i的引用
                  var j = i;
                  window.setTimeout(function(){
                            console.log(j);   //1,2,3
                  },0);
           })();
    }
    
    或者
    
    for(var i=0;i<3;i++){
           (function(i){
                  window.setTimeout(function(){
                            console.log(i);   //1,2,3
                  },0);
           })(i);
    } 

    模块:

      考虑一下示例:

    function model(){
           var msg = 'hello world';
           function alt(){
                  console.log(msg);
           }
           function reve(){
                  console.log(msg.split('').reverse());
           }
    
           return {
                alt:alt,
                rev:reve
           };
    }
    
    var mo = model();
    
    mo.alt();    //'hello world'
    
    mo.rev();   //["d", "l", "r", "o", "w", " ", "o", "l", "l", "e", "h"]

      这个模式在JavaScript中称为模块,最常见的实现模块模式通常被称为模块暴露。

      模块模式具备两个必要条件:

          1,必须有外部的封闭函数,该函数必须至少被调用一次(每次调用都会创建一个新的模块实例)。

          2,封闭函数必须至少返回一个内部函数,这样内部函数才能在私有作用域中形成闭包,并且可以访问或修改私有的状态。

      上面的例子每次调用外部封闭函数都会创建一个模块实例,如果只需要一个模块实例时:

    var mo = (function(){
           var msg = 'hello world';
           function alt(){
                  console.log(msg);
           }
           function reve(){
                  console.log(msg.split('').reverse());
           }
    
           return {
                alt:alt,
                rev:reve
           };
    })();
    
    mo.alt();    //'hello world'
    
    mo.rev();   //["d", "l", "r", "o", "w", " ", "o", "l", "l", "e", "h"]

    总结:当函数可以记住并访问所在的词法作用域,即使函数在当前词法作用域外调用,这是就产生了闭包。模块的两个主要特征:1,为创建内部作用域而调用一个包装函数。2,包装函数必须返回至少一个内部函数的引用,这样就会创建涵盖整个包装函数的内部作用域的闭包。

  • 相关阅读:
    Gitlab 自动化部署 + 局域网访问 gitlab pages
    Gitlab 跨版本升级
    Gitlab 私有化管理 npm 包
    Postman-请求加密和设置 Cookie
    menuStrip鼠标滑过自动弹出
    JAVA实用案例之文件导入导出(POI方式)
    springboot npoi 合并单元格 之后设置单元格居中
    postman测试导出Excel接口
    Application.DoEvents()的作用
    设置WINFORM窗体及程序图标
  • 原文地址:https://www.cnblogs.com/huangzhenghaoBKY/p/9818342.html
Copyright © 2011-2022 走看看