当前位置主页 > 资料库 > 前端教程 > JavaScript闭包-匿名函数和函数的作用域链

JavaScript闭包-匿名函数和函数的作用域链

12-24
匿名函数

在理解JavaScript的闭包之前,我们有必要了解一下JavaScript中函数的执行顺序。我们前面说过,定义函数有多种方式,其中最常用的是下面的两种方式。

/* 定义函数的第一种方式 */
function fn1(){
  alert("fn1");
}

/* 定义函数的第二种方式 */
var fn2 = function(){
  alert("fn2");
}                              
                            

对于第一种定义函数的方式,我们称为函数声明。以这种方式声明的函数会在函数执行之前被加载到内存中,所以无论是在函数定义之前,还是在函数定义之后调用这个函数都不会报错。

对于第二种定义函数的方式,我们称为函数表达式。以这种方式定义的函数会先在内存中创建一块区域,之后通过一个fn2的变量来指向这块区域,这块区域的函数开始是没有名称的,这种函数就叫做匿名函数,也叫作拉姆达(lambda)函数。如果我们在创建函数之前调用fn2(),那么程序会报错。

函数的作用域链

在JavaScript中,当进行函数的调用时,会创建一个执行环境,并为每一个函数增加一个属性SCOPE,通过这个属性来指向一块内存,这块内存中包含有所有上下文的变量。当在某个函数中调用了新函数之后,新函数依然会有一个作用域来执行原来的函数的SCOPE和自己新增加的SCOPE,这样就形成了一个链式结构,这就是JavaScript中的作用域链

每一个函数都有自己的执行环境。当执行流进入一个函数时,函数的环境就会被推入一个环境栈中。在函数执行完毕后,栈将它的环境弹出,把控制权交回给原来的执行环境。

作用域链的用途是保证对执行环境有权访问的所有变量和函数的有序访问。在作用域链的最前端始终是当前执行的代码所在的环境的变量对象。作用域链的下一个变量对象来自于包含环境,再下一个变量对象又来自于下一个包含环境,一直延续到全局执行环境。全局执行环境的变量对象始终是作用域链的最后一个对象。

上面的这几段话是什么意思呢?我们还是通过具体的例子和内存模型分析来讲解。先看下面的例子,下面的这个例子完成的功能是简单的交换color属性的颜色:

// 定义一个颜色属性
var color = "red";

// 定义显示颜色的方法
var showColor = function(){
  console.info(this.color);
} 

/* 定义一个交换颜色的函数 */
function changeColor(){
  var anotherColor = "blue";
  function swapColor(){
    var tempColor = anotherColor;
    anotherColor = color;
    color = tempColor;
  }
  swapColor();
}

// 调用交换颜色的函数来改变颜色
changeColor();

// 调用showColor()方法
showColor();                              
                            

我们来看上面的一段代码,代码中首先定义了一个颜色变量color,和一个用于打印颜色的方法showColor()。然后又定义了一个用于交换颜色的函数changeColor(),它的作用是将全局作用域中的颜色“red”修改为“blue”。注意在这个函数中是通过另外一个函数swapColor()来实现交换的。

再接下来,我们开始执行changeColor()函数。上面说到,js在执行函数的时候,会创建一个执行环境,并为每一个函数增加一个属性SCOPE,通过这个属性来指向一块内存,这块内存中包含有所有上下文的变量。那么,在执行changeColor()函数的时候,内存模型应该如下图所示:

函数的作用域链-1

图中蓝色部分是changeColor()函数的作用域链,由于changeColor()的执行上下文是window对象,所以它的作用域链的最高位指向的是全局作用域(golbal scope)。在我们的程序中,目前全局作用域中有colorshowColorchangeColor这3个属性。

changeColor()作用域链的低位指向的是它自己的作用域。在changeColor()中,有anotherColorswapColor2个属性。

接下来开始执行changeColor()函数,在函数内部又创建了一个swapColor()函数,创建之后立刻执行这个函数。此时的作用域链内存模型如下图所示:

函数的作用域链-2

同样,swapColor的作用域链的最顶端指向的是全局作用域,下一级指向的是包含它的changeColor函数的作用域,最后才是指向自己的作用域。

接着,swapColor函数开始执行,第一代码是var tempColor = anotherColor,它首先会在自己的作用域中查找是否有tempColor属性,根据上面的图我们可以看到,在swapColor的作用域中存在tempColor属性,于是它把tempColor的值由“red”修改为“blue”。

第二句代码是anotherColor = color,首先它也是先在swapColor的作用域中查找anotherColor属性,发现没有找到,它就会通过作用域链到上一级的changeColor作用域中去查找,找到之后将anotherColor属性由“blue”修改为“red”。

第三句代码是color = tempColor,属性查找的方法相同,首先在自己的作用域中查找,没有找到的话到上一级的作用域去查找。最终会在全局作用域中找到color属性,于是它将全局作用域中的color属性由“red”修改为“blue”。

最后,swapColor函数执行完毕之后,函数会被垃圾回收,同时changeColor函数也执行完毕,同样被垃圾回收。紧接着我们调用了showColor()方法,此时又会为该函数创建新的执行环境和作用域链。

showColor的作用域链中有2个指向:顶层的全局作用域和它自己的作用域。在执行showColor函数的时候,它在自己的作用域中没有发现color属性,于是到上一级的全局作用域中查找,此时全局作用域中的color属性已经被修改为“blue”,所以程序最终会打印出的颜色是“blue”。

相关阅读
Previous:
上一篇:JavaScript面向对象-基于原型链和函数伪装组合的方式实现继承
Next:
下一篇:JavaScript闭包-闭包的作用域
返回顶部