要想理解闭包,应当先理解JavaScript的作用域和作用域链。

  JavaScript有一个特性被称之为“声明提前(hoisting)”,即JavaScript函数里声明的所有变量(但不涉及赋值)都被“提前”至函数体的顶部,“声明提前”这步操作是在JavaScript引擎的“预编译”时进行的,是在代码开始运行之前,看一下下面的例子:

var name = "YY";
function getName(){
console.log(name); //输出undefine,而不是“YY”
var name = "Crucify";
console.log(name); //输出“Crucify”
}

  首先局部变量定义了一个和全局变量相同名字的变量,则在函数体内部局部变量遮盖了同名的全局变量,然后在函数体内部变量name的声明被提前至函数体顶部但并没有赋值,所以此时name是一个只被声明但并没有初始化的变量,我们知道变量只进行声明但并不初始化则它的值为undefine,所以第一行打印时undefine,下一行开始为变量name进行赋值,所以第二行打印的输出是我们所期望的。

  当某个函数被调用时,会创建一个执行环境及相应的作用域链。

  执行环境(execution context)定义了变量或函数有权访问的其他数据,决定了它们各自的行为。每个函数都有自己的执行环境。当执行流进入一个函数时,函数的环境就会被推入一个环境栈中。而在函数执行之后,栈将其环境弹出,把控制权返回给之前的执行环境。当代码在一个环境中执行时,会创建变量对象的一个作用域链(scope chain)。作用域链的用途,是保证对执行环境有权访问的所有变量和函数的有序访问。

  作用域链本质上是一个指向变量对象的指针列表,它只引用但不实际包含变量对象。当JavaScript需要查找变量x的值的时候(这个过程称作“变量解析”(variable resolution)),他会从列表之中的第一个对象开始查找直到最后一个对象,如果某个对象有一个名为x的属性,则会直接食用这个属性的值。如果作用域链上没有任何一个对象含有属性x,那么就认为这段代码的作用域上不存在x,并最终抛出一个引用错误异常。

  所谓的“变量对象的指针列表”很好理解。在JavaScript的最顶层代码中(也就是不包含在任何函数定义内的代码),作用域链是由一个全局对象组成。在不包含嵌套的函数体内,作用域链上有两个对象,第一个是定义函数参数和局部变量的对象,第二个是全局对象。在一个嵌套的函数体内,作用域链上则至少有三个对象,看下面的例子:

var name = "YY";
function getName(){
var name = "Crucify";
function f(){
return name;
}
return f();
}

  函数f()的作用域链上有三个对象,第一个是定义函数f()参数和局部变量的对象,第二个是定义函数getName()参数和局部变量的对象,第三个是全局对象。

  每个环境都可以向上搜索作用域链,以查询变量和函数名,但任何环境都不能通过向下搜索作用域链而进入另一个执行环境,即函数f()可以向上搜索函数getName()和全局对象中的属性,但是全局对象不能向下搜索getName()和f()中的值。

  而创建闭包的常见方式,就是在一个函数内部创建另一个函数。闭包是指有权访问另一个函数作用域中的变量的函数。

  一般来讲,当函数执行完毕后,局部活动对象就会被销毁,内存中仅保存全局作用域(全局执行环境的变量对象)。但是,闭包的情况有所不同,因为在另一个函数内部定义的函数会将包含函数(即外部函数)的活动对象添加到它的作用域链中,参考下面的代码:

function createComparisonFunction(propertyName) {
return function(object1, object2){
var value1 = object1[propertyName];
var value2 = object2[propertyName];
if (value1 < value2){
return -1;
} else if (value1 > value2){
return 1;
} else {
return 0;
}
};
}

当下列代码执行时,包含函数与内部匿名函数的作用域链如图所示:

var compare = createComparisonFunction("name");
var result = compare({ name: "Nicholas" }, { name: "Greg" });

  当createComparisonFunction()函数在执行完毕后,其活动对象也不会被销毁,因为匿名函数的作用域链仍然在引用这个活动对象。直到匿名函数被销毁后, createComparisonFunction()的活动对象才会被销毁:

compare = null;  //解除对匿名函数的引用(以便释放内存)

  由于闭包会携带包含它的函数的作用域,因此会比其他函数占用更多的内存。过度使用闭包可能会导致内存占用过多,所以在绝对必要时再考虑使用闭包。

  作用域链的这种配置机制引出了一个值得注意的副作用,即闭包只能取得包含函数中任何变量的最后一个值。因为闭包所保存的是整个变量对象,而不是某个特殊的变量:

function createFunctions(){
var result = new Array();
for (var i=0; i < 10; i++){
result[i] = function(){
return i;
};
}
return result;
}

  这个函数会返回一个函数数组,且每个函数都返回 10。

  在闭包中使用 this 对象也可能会导致一些问题。我们知道, this 对象是在运行时基于函数的执行环境绑定的,而匿名函数的执行环境具有全局性,因此其 this 对象通常指向 window(在通过 call()或 apply()改变函数执行环境的情况下, this 就会指向其他对象),看下面的例子:

var name = "The Window";
var object = {
name : "My Object",
getNameFunc : function(){
return function(){
return this.name;
};
}
};
alert(object.getNameFunc()()); //"The Window"(在非严格模式下)

  把外部作用域中的 this 对象保存在一个闭包能够访问到的变量里,就可以让闭包访问该对象了,如下所示:

var name = "The Window";
var object = {
name : "My Object",
getNameFunc : function(){
var that = this;
return function(){
return that.name;
};
}
};
alert(object.getNameFunc()()); //"My Object"

arguments 也存在同样的问题。如果想访问作用域中的 arguments 对象,必须将对该对象的引用保存到另一个闭包能够访问的变量中。

JavaScript 中的闭包和作用域链(读书笔记)的更多相关文章

  1. JavaScript中的闭包和作用域链

    这部分几乎是JavaScript中最难的部分,也是面试官最爱问的地方. 下面的内容是我以前写的<JavaScript学习手册>中被客户删除的部分,理由听起来有点诡异:太难.

  2. JavaScript 中的事件类型5(读书笔记思维导图)

    Web 浏览器中可能发生的事件有很多类型.如前所述,不同的事件类型具有不同的信息,而“ DOM3级事件”规定了以下几类事件. UI(User Interface,用户界面)事件:当用户与页面上的元素交 ...

  3. JavaScript 中的事件类型4(读书笔记思维导图)

    Web 浏览器中可能发生的事件有很多类型.如前所述,不同的事件类型具有不同的信息,而“ DOM3级事件”规定了以下几类事件. UI(User Interface,用户界面)事件:当用户与页面上的元素交 ...

  4. JavaScript 中的事件类型3(读书笔记思维导图)

    Web 浏览器中可能发生的事件有很多类型.如前所述,不同的事件类型具有不同的信息,而“ DOM3级事件”规定了以下几类事件. UI(User Interface,用户界面)事件:当用户与页面上的元素交 ...

  5. JavaScript 中的事件类型2(读书笔记思维导图)

    Web 浏览器中可能发生的事件有很多类型.如前所述,不同的事件类型具有不同的信息,而“ DOM3级事件”规定了以下几类事件: UI(User Interface,用户界面)事件:当用户与页面上的元素交 ...

  6. JavaScript 中的事件类型1(读书笔记思维导图)

    Web 浏览器中可能发生的事件有很多类型.如前所述,不同的事件类型具有不同的信息,而“ DOM3级事件”规定了以下几类事件. UI(User Interface,用户界面)事件:当用户与页面上的元素交 ...

  7. Javascript中闭包的作用域链

    作用域定义了在当前上下文中能够被访问到的成员,在Javascript中分为全局作用域和函数作用域,通过函数嵌套可以实现嵌套作用域. 闭包一般发生在嵌套作用域中.闭包是JavaScript最强大的特性之 ...

  8. Javascript——闭包、作用域链

    1.闭包:是指有权访问另一个函数作用域中的变量的函数.创建闭包的常见方式:在一个函数内部创建另一个函数. function f(name){ return function(object){ var ...

  9. 深入理解javascript中执行环境(作用域)与作用域链

    深入理解javascript中执行环境(作用域)与作用域链 相信很多初学者对与javascript中的执行环境与作用域链不能很好的理解,这里,我会按照自己的理解同大家一起分享. 一般情况下,我们把执行 ...

随机推荐

  1. 基于visual Studio2013解决面试题之1503最大公约数最小公倍数

     题目

  2. JVM类加载过程学习总结

    JVM类加载过程学习总结 先不说JVM类加载的原理,先看实例: NormalTest类,包含了一个静态代码块,执行的任务就是打印一句话. /** * 在正常类加载条件下,看静态代码块是否会执行 * @ ...

  3. javascript 偏移量

    通过四个属性可以获得元素的偏移量: 1.offsetHeight: 元素在垂直方向上占用的空间的大小,(像素).包括元素的高度,(可见的)水平滚动条的高度,上边框高度和下边框高度. 2.offsetW ...

  4. 安全威胁无孔不入:基于Linux系统的病毒(转)

    虽然在Linux里传播的病毒不多,但也是存在一些.我从一些安全网站搜集了一些资料. 1.病毒名称: Linux.Slapper.Worm 类别: 蠕虫 病毒资料: 感染系统:Linux 不受影响系统: ...

  5. Swift - 判端网络连接状态,连接类型(3G还是Wifi)

    IJReachability是一个使用Swift写的第三方网络检测类.可以测试网络是否连接,并支持3G和Wifi的检测. 使用样例: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 ...

  6. UVA 2039 Pets(网络流)

    Problem Description Are you interested in pets? There is a very famous pets shop in the center of th ...

  7. windows 2003 DNS服务的重建

    DNS的重建 网络上转载之文章,据说来自微软官方,留下一份自己用,曾经帮助我解决了DNS方面的困扰,希望能给大家带来帮助 1.打开dns管理器,删除domain.com区域,如果存在_msdcs.do ...

  8. haproxy timeout server 46000 后台超时时间

    [root@wx03 ~]# sh ./1.sh Wed Jul 6 19:54:40 CST 2016 <html><body><h1>504 Gateway T ...

  9. 1.0.2-学习Opencv与MFC混合编程之---为播放AVI视频添加滑动条

    源代码地址:http://download.csdn.net/detail/nuptboyzhb/3961642 版本1.0.2新增内容 Ø  全局变量和函数的添加: 在CVMFCview.cpp文件 ...

  10. android面试题目大全<完结部分>,android笔试题目集锦

    1. 下列哪些语句关于内存回收的说明是正确的? (b ) A. 程序员必须创建一个线程来释放内存   B.内存回收程序负责释放无用内存    C.内存回收程序允许程序员直接释放内存    D.内存回收 ...