初学闭包时一直以为很简单。但伴随对一个问题深入学习后,才算真正理解了闭包,同时也发现连<<JavaScript高级程序设计>>中都些不准确的地方。

我不准备从头介绍闭包的概念,而是在下面列了几份参考资料。其中以【参考2】最为简洁,本文也是因文中的习题而引出进一步的探讨。

从[参考2]最后提出的习题开始(应该来自<<JavaScript高级 程序设计>> 7.2),期望下面的程序可以输出"My Object",并且预期在取得this.name值时的标识符解析(identifier resolution)顺序如下:

(示例1)

上面程序会在log位置输出"The Window", 而不是期望的"My Object"。

其实红色部分错了。this并不指向创建它的对象,而是指向执行它的对象。
所以在执行下面这句话时,this其实是全局的window:

object.getNameFunc()();

这句话等价于以下两句:

vartempFunc = object.getNameFunc();

tempFunc();

它的执行对象就是全局的window,并且tempFunc和window.tempFunc完全等价的。

其执行对象的变化示意如下:

getNameFunc的执行对象是object, 而返回的闭包函数的执行对象是window。

增加一个示例,可以更明确的了解到这一点:
var name =
"The Window";

var object = {

 name :
"My Object",

getNameFunc:function() {

console.log(this.name);  // The Window

}

};

window.onload = object.getNameFunc;

(示例2)

所以如果要达成期望的输出,必须改变this的指向。有两种做法:
  1. 不用this,而使用参数从父函数传入所需的数据。(不用this,不是简单将this去除。默认还是会使用this访问的。)
  2. 显示改变this 指向 (比如call或apply)。

第一种方法的示例:

var name ="The Window";

var object = {

  name :"My Object",

getNameFunc:function() {

var that =this;

return function(){

return that.name;

};

}

 };

 console.log( window.object.getNameFunc()());

(示例3)

第二种方法的示例:
  1. 将调用方法改为如下:

console.log( object.getNameFunc().call(object) );

2. 简化调用,改为如下:

var name ="The Window";

var object = {

    name :
"My Object",

getNameFunc:function() {

return(function(){

returnthis.name;

}).call(this);

}

 };

console.log( object.getNameFunc());

(示例4)

到底发生了什么?

<<JavaScript高级程序设计>>:

某个函数第一次被调用时,会创建一个执行环境(execution context,环境比上下文更直白)及相应的作用域链(scope
chain),并把作用域(scope)赋值给一个特殊的内部属性([[Scope]])。然后,使用this、arguments和其它命名参数的值来
初始化函数的活动对象(activation object)。但在作用域链中,外部函数(outer)的活动对象(activation
object)处于第二位,外部函数的外部函数的活动对象处于第三位,……直至作为作用域链终点的全局执行环境。(7.2, 3rd Edition)

执行环境包含了三个部分 [详细内容在【参考4】中]:
  a. 词法环境
  b. 变量环境
  c. ThisBinding

(图片来自【参考4】)

如果这样,示例的理解就应该是正确!所以,显然不是这么单纯。再思考一下函数的创建时机。函数的声明与其实例化(instantiation)的时间是不同的,而是在使用时创建。实例化过程中发生了什么?

[参考5]中给的解释:  与外部函数声明对应的函数对象会在全局执行环境的变量实例化过程中被创建。因此,外部函数对象的 [[scope]] 属性中会包含一个只有全局对象的“单项目(one item)”作用域链。
这样就对了。下面就是name的查找顺序(和示例1的this.name不同)。

如下面的示例所示:

var name ="The Window";

var object = {

  name :"My Object",

getNameFunc:function() {

var name="Inner Of getNameFunc";

return function(){

console.log(name); //Inner Of getNameFunc

return name;

};

}

 };

 console.log( window.object.getNameFunc()()); //Inner Of getNameFunc

(示例5)

References:
1. PPK 谈 JavaScript 的 this 关键字
 http://www.cnblogs.com/georgewing/archive/2009/09/29/1576641.html
2. 学习Javascript闭包(Closure)
http://www.ruanyifeng.com/blog/2009/08/learning_javascript_closures.html
3.  闭包的秘密
http://www.gracecode.com/archives/2385/
4. JavaScript Closures Explained
 http://lostechies.com/derekgreer/2012/02/17/javascript-closures-explained/
5. 理解JavaScript闭包
 http://www.cn-cuckoo.com/main/wp-content/uploads/2007/08/JavaScriptClosures.html

【javascript闭包】转载一篇不错的解释,也有几个大牛的链接的更多相关文章

  1. javascript 闭包 转载

    http://www.jb51.net/article/24101.htm var name = "the window"; var object = { name:"m ...

  2. 我从来不理解JavaScript闭包,直到有人这样向我解释它...

    摘要: 理解JS闭包. 原文:我从来不理解JavaScript闭包,直到有人这样向我解释它... 作者:前端小智 Fundebug经授权转载,版权归原作者所有. 正如标题所述,JavaScript闭包 ...

  3. 我从来不理解 JavaScript 闭包,直到有人这样向我解释它...

    正如标题所述,JavaScript 闭包对我来说一直有点神秘,看过很多闭包的文章,在工作使用过闭包,有时甚至在项目中使用闭包,但我确实是这是在使用闭包的知识. 最近看国外的一些文章,终于,有人用于一种 ...

  4. JavaScript闭包,只学这篇就够了

    # 闭包不是魔法 这篇文章使用一些简单的代码例子来解释JavaScript闭包的概念,即使新手也可以轻松参透闭包的含义. 其实只要理解了核心概念,闭包并不是那么的难于理解.但是,网上充斥了太多学术性的 ...

  5. JavaScript闭包只学这篇就够了

    闭包不是魔法 这篇文章使用一些简单的代码例子来解释JavaScript闭包的概念,即使新手也可以轻松参透闭包的含义. 其实只要理解了核心概念,闭包并不是那么的难于理解.但是,网上充斥了太多学术性的文章 ...

  6. javascript深入理解js闭包(转载)

    此篇文章来源于http://www.jb51.net/article/24101.htm 一.变量的作用域 要理解闭包,首先必须理解Javascript特殊的变量作用域. 变量的作用域无非就是两种:全 ...

  7. javascript闭包—围观大神如何解释闭包

    闭包的概念已经出来很长时间了,网上资源一大把,本着拿来主意的方法来看看. 这一篇文章 学习Javascript闭包(Closure) 是大神阮一峰的博文,作者循序渐进,讲的很透彻.下面一一剖析. 1. ...

  8. 最简明的JavaScript闭包解释

    最简明的JavaScript闭包解释 JavaScript是这几年最火的编程语言之一,从前端到服务器端,再到脚本,好像没有一个地方没有JavaScript的身影.这个世界上任何的一种事物的存在必然有其 ...

  9. 上一篇括号配对让人联想起catalan数,顺便转载一篇归纳的还不错的文章

    转载请注明来自souldak,微博:@evagle 怎么样才是合法的组合? 只要每一时刻保证左括号的数目>=右括号的数目即可. 直接递归就行,每次递归加一个括号,左括号只要还有就能加,右括号要保 ...

随机推荐

  1. emqtt 分布集群及节点桥接搭建

    目录 分布集群 emq@s1.emqtt.io 节点设置 emq@s2.emqtt.io 节点设置 节点加入集群 节点退出集群 节点发现与自动集群 manual 手动创建集群 基于 static 节点 ...

  2. 【异常】Caused by: java.lang.IllegalStateException: Method has too many Body parameters

    出现此异常原因是引文使用feign客户端的时候,参数没有用注解修饰 1.1GET方式错误写法 @RequestMapping(value="/test", method=Reque ...

  3. 2019-3-1-win10-uwp-使用-LayoutTransformer

    title author date CreateTime categories win10 uwp 使用 LayoutTransformer lindexi 2019-03-01 09:24:32 + ...

  4. 深度探索C++对象模型第四章:函数语义学

    C++有三种类型的成员函数:static/nonstatic/virtual 一.成员的各种调用方式 C with class 只支持非静态成员函数(Nonstatic member function ...

  5. js 在array的遍历操作中修改arry中元素数量 出现的一些奇特的操作

    在js中array是属于复杂类型,在arr1=arr2得赋值操作中,arr1得到的值并不是arr2的value,而是一个指向引用.那么修改arr1的同时arr2读取的值也会同步变化,那么问题来了,上代 ...

  6. PHP上传文件和下载

    PHP 中文件上传的基础知识: 1)客户端 form 表单设置 必须设置的 form 表单项: <!DOCTYPE html> <html> <head> < ...

  7. linux su su-的区别

    su只是切换用户. su - 切换用户并切换shell环境. su another pwd后为/home/current su - another pwd后为/home/another

  8. 【Flutter学习】之 Flutter 的生命周期

    一,概述 Flutter 的生命周期分为两个部分: Widget 的生命周期 App 的生命周期 二,Widget 的生命周期 Flutter 里的 Widget 分为 StatelessWidget ...

  9. 20175223 《Java程序设计》第十一周学习总结

    目录 教材学习内容总结 代码调试中的问题和解决过程 1. Linux中编程实现计算器方法乘法报错,但 IDEA 中可以. [代码托管] 学习进度条 参考资料 目录 教材学习内容总结 因未熟练掌握第十章 ...

  10. PHP面试 javascript和jQuery 基础

    JavaScript基础 JavaScript语法 变量的定义:变量必须以字母开头   可以使用$和 _ 符号开头  变量名称对大小写敏感 使用 var 关键词声明变量   可以一次声明多个变量    ...