• JavaScript的“闭包”到底是什么(2)


    我的上篇博客标题不对,造成一些误解。我认为博客的宗旨不是背教科书,而是分享研发心得。我的上篇标题因该改成“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 于西雅图

  • 相关阅读:
    vue 加载更多2
    vue 加载更多
    js获取浏览器信息
    iscroll
    git fetch
    input file accept类型
    git从安装到使用
    sass中的循环判断条件语句
    animation
    vue2+animate.css
  • 原文地址:https://www.cnblogs.com/ly8838/p/3939458.html
Copyright © 2020-2023  润新知