视频1 视频21 视频41 视频61 视频文章1 视频文章21 视频文章41 视频文章61 推荐1 推荐3 推荐5 推荐7 推荐9 推荐11 推荐13 推荐15 推荐17 推荐19 推荐21 推荐23 推荐25 推荐27 推荐29 推荐31 推荐33 推荐35 推荐37 推荐39 推荐41 推荐43 推荐45 推荐47 推荐49 关键词1 关键词101 关键词201 关键词301 关键词401 关键词501 关键词601 关键词701 关键词801 关键词901 关键词1001 关键词1101 关键词1201 关键词1301 关键词1401 关键词1501 关键词1601 关键词1701 关键词1801 关键词1901 视频扩展1 视频扩展6 视频扩展11 视频扩展16 文章1 文章201 文章401 文章601 文章801 文章1001 资讯1 资讯501 资讯1001 资讯1501 标签1 标签501 标签1001 关键词1 关键词501 关键词1001 关键词1501 专题2001
JavaScript闭包-匿名函数和函数的作用域链
2020-11-27 20:26:16 责编:小采
文档


匿名函数

在理解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()函数的时候,内存模型应该如下图所示:

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

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

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

同样,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”。

下载本文
显示全文
专题