JavaScript中词法做用域、闭包与跳出闭包

本文从属于笔者的JavaScript 入门与最佳实践系列文章,同时,本部份内容也概括于笔者的个人校招准备之路:从Web前端到服务端应用架构这篇综述。javascript

大部分人都会作错的经典JS闭包面试题
how-do-javascript-closures-workhtml

Lexical Scope:词法做用域

functions are executed using the scope chain that was in effect when they were defined前端

通常来讲,在编程语言里咱们常见的变量做用域就是词法做用域与动态做用域(Dynamic Scope),绝大部分的编程语言都是使用的词法做用域。词法做用域注重的是所谓的Write-Time,即编程时的上下文,而动态做用域以及常见的this的用法,都是Run-Time,即运行时上下文。词法做用域关注的是函数在何处被定义,而动态做用域关注的是函数在何处被调用。JavaScript是典型的词法做用域的语言,即一个符号参照到语境中符号名字出现的地方,局部变量缺省有着词法做用域。此两者的对比能够参考以下这个例子:java

function foo() {
    console.log( a ); // 2 in Lexical Scope ,But 3 in Dynamic Scope
}

function bar() {
    var a = 3;
    foo();
}

var a = 2;

bar();

看一个实例以下:git

var scope = "I am global";
function whatismyscope(){
   var scope = "I am just a local";
   function func() {return scope;}
   return func;
}

whatismyscope()()

该代码片最终输出的结果是:github

I am just a local

Closure

闭包自己是含有自由变量的代码块,在JavaScript中咱们经常使用的闭包则是自己的词法做用域与变量保留相结合的表现,首先回顾下一个基本的词法做用域的用法:web

function init() {
  var name = "Mozilla";
  function displayName() {
    alert(name);
  }
  displayName();
}
init();

函数 init() 建立了一个局部变量 name,而后定义了名为 displayName() 的函数。displayName() 是一个内部函数——定义于 init() 以内且仅在该函数体内可用。displayName() 没有任何本身的局部变量,然而它能够访问到外部函数的变量,便可以使用父函数中声明的 name 变量。注意,这里是直接执行外部的init函数,下面看一个闭包的例子:面试

function makeFunc() {
  var name = "Mozilla";
  function displayName() {
    alert(name);
  }
  return displayName;
}

var myFunc = makeFunc();
myFunc();

运行这段代码的效果和以前的 init() 示例彻底同样:字符串 "Mozilla" 将被显示在一个 JavaScript 警告框中。其中的不一样 — 也是有意思的地方 — 在于 displayName() 内部函数在执行前被从其外围函数中返回了。这段代码看起来别扭却能正常运行。一般,函数中的局部变量仅在函数的执行期间可用。一旦 makeFunc() 执行事后,咱们会很合理的认为 name 变量将再也不可用。虽然代码运行的没问题,但实际并非这样的。这个谜题的答案是 myFunc 变成一个闭包了。 闭包是一种特殊的对象。它由两部分构成:函数,以及建立该函数的环境。环境由闭包建立时在做用域中的任何局部变量组成。在咱们的例子中,myFunc 是一个闭包,由 displayName 函数和闭包建立时存在的 "Mozilla" 字符串造成。编程

避免闭包

在真实的开发中咱们经常会使用闭包这一变量保留的特性来传递变量到异步函数中,不过闭包也每每会使程序出乎咱们的控制,譬如在下面这个简单的循环中,咱们本但愿可以打印出0~9这几个数:闭包

for(var i = 0;i < 10;i++){
   setTimeout(()=>{console.log(i),1000})
}

不过全部输入的i的值都是10,这与咱们的指望产生了很大的误差。所以咱们在部分状况下须要破坏闭包而获取真实的变量值。

将异步获取值保留到新增的闭包中

咱们能够考虑加一层闭包,将i以函数参数形式传递给内层函数:

function init3() {     
      var pAry = document.getElementsByTagName("p");     
      for( var i=0; i<pAry.length; i++ ) {     
       (function(arg){         
           pAry[i].onclick = function() {         
              alert(arg);     
           };     
       })(i);//调用时参数     
      }     
    }

或者在新增的闭包中将i以局部变量形式传递给内部函数中:

function init4() {     
      var pAry = document.getElementsByTagName("p");     
      for( var i=0; i<pAry.length; i++ ) {       
        (function () {     
          var temp = i;//调用时局部变量     
          pAry[i].onclick = function() {       
            alert(temp);       
          }     
        })();     
      }     
    }

将变量值保留到做用域以外

在DOM环境中,咱们能够将变量值存储到要操做的DOM对象中:

function init() {     
      var pAry = document.getElementsByTagName("p");     
      for( var i=0; i<pAry.length; i++ ) {     
         pAry[i].i = i;     
         pAry[i].onclick = function() {     
            alert(this.i);     
         }     
      }     
    }

也能够将变量i保存在匿名函数自己:

function init2() {     
      var pAry = document.getElementsByTagName("p");     
      for( var i=0; i<pAry.length; i++ ) {       
       (pAry[i].onclick = function() {     
            alert(arguments.callee.i);     
        }).i = i;     
      }     
    }

相关文章
相关标签/搜索