学习笔记=>《你不知道的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,包装函数必须返回至少一个内部函数的引用,这样就会创建涵盖整个包装函数的内部作用域的闭包。

猜你喜欢

转载自www.cnblogs.com/huangzhenghaoBKY/p/9818342.html