要想理解闭包,应当先理解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. 【剑指offer】从上向下打印二叉树

    转载请注明出处:http://blog.csdn.net/ns_code/article/details/26089165 剑指offer上的第23题,实际上就是考察二叉树的层序遍历,详细思想能够參考 ...

  2. Qt 智能指针学习

    原地址:http://blog.csdn.net/dbzhang800/article/details/6403285 从内存泄露开始? 很简单的入门程序,应该比较熟悉吧 ^_^ #include & ...

  3. SRM 582 Div II Level Three: ColorTheCells, Brute Force 算法

    题目来源:http://community.topcoder.com/stat?c=problem_statement&pm=12581 Burte Force 算法,求解了所有了情况,注意  ...

  4. 圆角和圆形ImageView

    ※效果 ※代码 /** * 转换图片成圆形 * * @param bitmap * 传入Bitmap对象 * @return */ public Bitmap toRoundBitmap(Bitmap ...

  5. Serialize a Binary Tree or a General Tree

    For a binary tree, preorder traversal may be enough. For example, _    /   \           /     /  \ 50 ...

  6. 论文阅读笔记 - Mesos: A Platform for Fine-Grained ResourceSharing in the Data Center

    作者:刘旭晖 Raymond 转载请注明出处 Email:colorant at 163.com BLOG:http://blog.csdn.net/colorant/ 更多论文阅读笔记 http:/ ...

  7. Net Core-Razor

    几行代码解决Razor中的嵌套if语句 MVC开发中,经常会遇到在razor中插入简单的逻辑判断. @if (clientManager.IsAdmin) { if (!Model.Topic.Top ...

  8. freemarker的TemplateExceptionHandler使用

    系统使用freemarker作为页面展示层,为了解决系统统一异常的问题.于是配置了struts2的统一异常解决的方法(这个网上资料非常多,大家能够查看),但是发现freemarker出现异常后,str ...

  9. 积累的VC编程小技巧之工具提示

    1.用鼠标移动基于对话框的无标题栏程序的简单方法 void CVCTestDlg::OnLButtonDown(UINT nFlags, CPoint point) {    //一句话解决问题    ...

  10. 积累的VC编程小技巧之属性页

    1.属性页的添加: 创建对话框的类,该类要从CpropertyPage继承:然后在要添加该对话框为属性页的类(头文件)里创建CpropertySheet类的一个对象m_tabsheet和新创建的对话框 ...