我的上篇博客标题不对,造成一些误解。我认为博客的宗旨不是背教科书,而是分享研发心得。我的上篇标题因该改成“JavaScript 闭包的一个议题:它对outer scope 的影响”,因为我没有严格地去分析闭包的定义,而是分析了实现闭包的其中一个语义问题。

讲清楚闭包是件麻烦事,我也没有看到什么关于JavaScript的权威性著作(比如像C++语言有 Bjarne StroustrupC++ programming language)。所以除了苦读JavaScript语言国际标准Standard ECMA-262 specification,我无法推荐一个论述“闭包”的最好的教材。

网友“穆己”的“scope chaining”的确是比较接近实质,但也不全面。我只好抛砖引玉,再做一次企图。

闭包的含义包含了下列三个主要概念:

Lexical Scope and Scope Chain

Lexical Scope的概念并不是Javacript发明,但是它作为JavaScript函数的组成部分,是一个在“传统”函数概念上的附加值。

传统函数(C, C++, Java, C#等)的lexical scope 和runtime scope 是一样的。JavaScript 的lexical scope指的是函数定义时的“环境”,而不是函数运行时的环境。

对于一个特定函数来说,其”自由变量”是这个函数闭包中需要俘获的主要内容。自由变量(本函数没有定义的变量)的lexical capture(俘获)顺序是 (也就是scope chaining 的顺序):

A, 母函数的local 变量

B. 母函数的input argument

C.在母函数的母函数中重复A,B,直到最顶层(GLOBAL scope)

在下面 的 myObj  的定义中:

var x = 1000;                               // line 0

function myObj(x, y) {                // Line1

 

            this.func1 = function() { // Line2

                        x++;

                        y --;

            }

            this.get1 = function ()

            {

                        return x;

            }

            this.get2 = function ()

            {

                        return y;

            }

            var x = 0;                // Line 3

}

myObj.prototype.AddTwo = function(z)

{

            return this.get1() + this.get2() + z;

}

var m1 = new myObj(10, 20);      // Line 4

var m2 = new myObj(30, 70);      // Line 5

console.log('m1.x: ' + m1.get1());  // Line 6

console.log('m1.y: ' + m1.get2());  // Line 7

console.log('m2.x: ' + m2.get1());  // Line 8

console.log('m2.y: ' + m2.get2());  // Line 9

对于上面的例子,如果不是lexical scope, line 6 ~ line 9 打印的应该是10, 20, 30, 70。

但是因为lexical  scope俘获顺序,x 是0(见line 3), 所以打印的是:0, 20, 0, 70。

注释掉line3,根据俘获顺序,打印的就成了10, 20, 30, 70。

myObj(x, y)改成myObj(z, y)打印的就成了1000, 20, 1000, 70。其中 1000是从global里(Line 0)俘获的。

Lexical 俘获是在parsing stage进行的

上面的俘获顺序必须在函数的parsing阶段进行。函数的数据结构中在parsing后已经包含了所有“俘获变量的reference”,运行阶段不会改变了。这就是为什么上面的line 3定义的可以优先于input 参数x的原因。若是执行时capture, line 3 是在函数的定义之后,该capture的因该说是input 参数x了。

C,C++等编译语言是直接翻译成native 函数的,所有的函数运行信息都靠stack frame来动态获取。唯一和闭包有所接近的概念是“全程变量(global variable)”. 这些global变量在编译时也都转换成内存地址,运行时可以“就地解决”,无需一个独立的闭包。这些函数不是object,不需动态生成,所以无需一个“静态`”的闭包。

JavaScript之所以需要一个独立的闭包,本人认为是因为所有的JavaScript都是object,可以“动态生成”,但是定义(第一道parsing)却是静态的,这个“静态”的部分需要闭包,动态的部分和传统函数一样,靠runtime context 支撑。

这种“实现上的复杂性”,是为了闭包所带来的,处理异步事件时的方便付出的代价。

Lexical 俘获是reference不是value

这是我的上篇博客想要强调的地方。如果上面的myObj执行时,如果俘获的是x的值,那么这三个函数func1get1get2就不会有任何联系了。

 

因为俘获的是x的reference,  所以上面三个函数所看到的x是同一个变量。

这一点很重要,因为JavaScript中的local 变量并不是都是heap中的。起码 GOOGLE  V2 就不是。但是上面line 3 的x必须在heap中“出生和生活”,否则func1get1get2就会在已经毁灭了的stack 变量x上工作,使得上面的程序变得毫无意义了。

也就是说,闭包的runtime代价是将所被闭包的变量从stack中转到heap中。

总结

我认为,只有弄懂了闭包的上诉三个概念,才可以在闭包的应用上立于不败之地。

2014-8-26 于西雅图

JavaScript的“闭包”到底是什么(2)的更多相关文章

  1. JavaScript的“闭包”到底是什么

    在JavaScripot函数闭包的定义中,一般都有一个outer 函数,一个inner函数.那么“闭包”到底是指outer函数呢,还是指inner函数? 从官方定义来看,并不清楚:A closure  ...

  2. 彻底搞懂JavaScript的闭包、防抖跟节流

    最近出去面试了一下,收获颇多!!! 以前的我,追求实际,比较追求实用价值,然而最近面试,传说中的面试造火箭,工作拧螺丝,竟然被我遇到了.虽然很多知识点在实际工作中并不经常用到,但人家就是靠这个来筛选人 ...

  3. 深入理解JavaScript的闭包特性如何给循环中的对象添加事件

    初学者经常碰到的,即获取HTML元素集合,循环给元素添加事件.在事件响应函数中(event handler)获取对应的索引.但每次获取的都是最后一次循环的索引.原因是初学者并未理解JavaScript ...

  4. JavaScript作用域闭包简述

    JavaScript作用域闭包简述 作用域 技术一般水平有限,有什么错的地方,望大家指正. 作用域就是变量起作用的范围.作用域包括全局作用域,函数作用域以块级作用域,ES6中的let和const可以形 ...

  5. JavaScript的闭包原理

    什么是js(JavaScript)的闭包原理,有什么作用? 一.定义 官方解释:闭包是一个拥有许多变量和绑定了这些变量的环境的表达式(通常是一个函数),因而这些变量也是该表达式的一部分. 个人的理解是 ...

  6. Js(javaScript)的闭包原理

    问题?什么是js(javaScript)的闭包原理,有什么作用? 一.定义 官方解释:闭包是一个拥有许多变量和绑定了这些变量的环境的表达式(通常是一个函数),因而这些变量也是该表达式的一部分.  小编 ...

  7. 深入理解javascript的闭包

    闭包(closure)是Javascript语言的一个难点,也是它的特色,很多高级应用都要依靠闭包实现. 一.变量的作用域 要理解闭包,首先必须理解Javascript特殊的变量作用域. 变量的作用域 ...

  8. 如何给循环中的对象添加事件--深入理解JavaScript的闭包特性

    初学者经常碰到的,即获取HTML元素集合,循环给元素添加事件.在事件响应函数中(event handler)获取对应的索引.但每次获取的都是最后一次循环的索引.原因是初学者并未理解JavaScript ...

  9. javascript,jquery(闭包概念)(转)

    偶尔听人说javascript闭包,让我联想起以前学编译原理和数字逻辑里讲的闭包,以前上课讲的闭包很难懂,而且含有递归的意思在里面,现在不想再查看里面的闭包概念. 但javascript我是经常要用, ...

随机推荐

  1. [置顶] 推荐12款很棒的HTML5开发框架和开发工具

    HTML5 在不同的领域让网页设计更强大的.快速,安全,响应式,互动和美丽,这些优点吸引更多的 Web 开发人员使用 HTML5.HTML5 有许多新的特性功能,允许开发人员和设计师创建应用程序和网站 ...

  2. Unity NGUI制作scroll view

    unity版本:4.5 NGUI版本:3.6.5 参考链接:http://blog.csdn.net/monzart7an/article/details/23878505,作者:CSDN 冬菊子   ...

  3. Balance(01背包)

    Time Limit: 1000MS   Memory Limit: 30000K Total Submissions: 9163   Accepted: 5617 Description Gigel ...

  4. 多台计算机之间的ssh无密钥登录

    在很多分布式系统中,我们最常遇到的一个问题是,需要在服务器集群上保证多台机器之间的SSH无密钥登录.以Hadoop为例,为了方便,我们需要在master和slaves之间配置密钥登录,这样我们启动Ha ...

  5. CentOS6.5 mini开启网络

    1.编辑network配置 vim /etc/sysconfig/network-scripts/ifcfg-eth0 1 2 3 4 5 6 7 DEVICE=eth0 HWADDR=00:0C:2 ...

  6. IFTT-意大利金融交易税

    港交所公告,IFTT目前适用于Prada股份及其4只权证,所有于今年3月1日或之后买入Prada的投资者均要缴付IFTT.就股份而言,IFTT税率为交易价值的0.22%(2014年1月1日起将降至0. ...

  7. android——wifi系统架构

    1. 系统架构 Android WiFi系统引入了wpa_supplicant,它的整个WiFi系统以wpa_supplicant为核心来定义上层用户接口和下层驱动接口.整个WiFi系统架构如下图所示 ...

  8. POJ - 1170 Shopping Offers (五维DP)

    题目大意:有一个人要买b件商品,给出每件商品的编号,价格和数量,恰逢商店打折.有s种打折方式.问怎么才干使买的价格达到最低 解题思路:最多仅仅有五种商品.且每件商品最多仅仅有5个,所以能够用5维dp来 ...

  9. Ubuntu server下安装JDK和Tomcat7

    服务器是Ubuntu server 12.04 LTS 64bit 所有操作假设已经有root权限,若没有需要添加sudo. 一. 安装JDK 1.去Oracle官网下载jdk-6u45-linux- ...

  10. oracle暂时表空间 ORA-01652:无法通过16(在表空间XXX中)扩展 temp 字段

    今天在查数据的时候报错  ORA-01652:无法通过16(在表空间temp1中)扩展 temp 字段 查看表空间使用明细 SELECT b.tablespace,        b.segfile# ...