zoukankan      html  css  js  c++  java
  • JavaScript执行上下文、作用域链与闭包

    根据JavaScript高级程序设计,JavaScript核心以及汤姆大叔的深刻理解JavaScript系列总结。执行上下文、变量对象、作用域链等是从程序运行的角度解释的。

    执行上下文

      执行上下文(EC)顾名思义是指程序代码执行时候的环境,分为全局执行上下文、函数执行上下文以及eval执行上下文,每个函数都有执行上下文,当JavaScript程序执行不同的函数时会进入不同的执行上下文,一个执行上下文可能触发另外的执行上下文,比如一个函数调用另一个函数,故会产生上下文堆栈。
      一个函数可能会创建无数的上下文,因为对函数的每次调用(即使这个函数递归的调用自己)都会生成一个具有新状态的上下文。

    执行上下文栈

      程序开始执行时会进入全局上下文(唯一的),全局上下文是执行上下文栈的第一个元素,当全局上下文执行过程中调用了一个函数,则该函数的执行上下文就会push到执行上下文栈中,当该函数又调用其他函数时,会进入新的执行上下文,并被push到栈中,当函数调用完成时会退出该执行上下文,进入外层的执行上下文继续执行,故栈底始终是全局上下文,栈顶始终是当前程序正在运行的执行上下文。
    以程序为例:

        var x=10;
        function a() {
            var y=20;
            function b() {
                var z=10;
                console.log(x);
            }
            b()
        }
        a();
    

    这个程序的执行上下文的变化是这样的:


    栈底的元素始终是全局执行上下文,栈顶的元素始终是正在执行的函数的执行上下文。

    变量对象

      每一个执行上下文可以看成简单的对象,都有三个重要属性,变量对象,this,作用域链scope chain。在全局上下文中变量对象属性(variable Object,简称VO)包含全局变量和函数声明,不包含在内的有函数表达式以及没有使用var声明的变量(这种变量是,"全局"的声明方式,只是给Global添加了一个属性,并不在VO中)。
      函数执行上下文中变量对象又叫活动对象(activitive Object,简称AO),活动对象包含arguments,传进去的形参,以及函数内部自定义的变量和函数声明。

        var x=10;
        function a() {
            var w=30;
            var y=20;
            function b(p,q) {
                var z=10;
                console.log(p+q);
            }
            b(w,y)
        }
        a();
    

    下图是全局变量对象与函数a,函数b的活动对象。

    作用域链

      作用域链是一个对象列表,是内部上下文所有变量对象(包括父变量对象)的列表。上下文代码中出现的标识符在这个列表中进行查找。解析标识符(变量名称、函数声明、形参等)时,会从作用域链中当前活动变量对象中查找,如果没有找到则会向作用域链的上一层查找,直到找到为止。
      这里作用域链用Scope表示,它包括两部分,一是当前执行上下文的变量/活动对象用VO/AO表示,二是函数内部有一个特殊的[[Scope]]属性,保存父级的作用域链(包含父级变量对象+[[Scope]]),当函数创建的时候,函数内部特殊的[[Scope]]属性就存储了父级的作用域链,并且是静态的,即Scope=VO/AO+[[Scope]]。

    可以看到当前[[Scope]]保存的是父级的整个作用域,包含父级的活动对象以及其函数的[[Scope]]属性,也就是说当前的执行上下文中包含所有的活动对象和全局变量对象。

    作用域链与原型链的二维查找

      当这些函数或者对象拥有原型时,原型链和作用域链的查找顺序是:在当前执行上下文中会先查找该执行上下文的活动变量,然后查找其原型链__proto__(如果有原型链),如果原型链上没有,再查找[[Scope]]中活动对象父作用域链,深入每一个作用域链的原型链。即活动对象→原型链→[[Scope]]中父级活动对象→[[Scope]]中父级原型链→[[Scope]]中上一级的[[Scope]]中父级活动对象……

    闭包

      在函数创建时[[Scope]]属性就存储了父级的作用域链,这样返回这个函数时就可以访问父函数的作用域,这就是闭包。理论上来说,由于所有的函数都有[[Scope]]属性,所有的函数都能看成闭包(由于全局作用域的存在)。通过new Function()函数创建的函数的[[Scope]]属性保存的总是全局对象的作用域链。
      多个函数可能拥有相同的父作用域(这是很常见的情况,比如for循环传递i的值到内部函数的时候)。在这种情况下,它们的父作用域链是相同的,[[Scope]]属性中存储的变量是在拥有相同父作用域链的所有函数之间共享的。即在这个函数里改变了变量的值,另一个函数中也改变了。立即执行函数可以改变相同父作用域的共享,因为立即执行函数相当于新建了一个作用域链,那么就不是相同的父作用域链了。而且在所有函数中参数的传递都是值传递。

    参考:
    http://weizhifeng.net/javascript-the-core.html#execution-context-stack
    http://www.cnblogs.com/tomxu/archive/2011/12/15/2288411.html

  • 相关阅读:
    北京清北 综合强化班 Day1
    Noip2015 提高组 Day1
    Noip2016 提高组 Day2 T1 组合数问题
    2017.9.23 NOIP2017 金秋杯系列模拟赛 day1 T1
    [51NOD1103] N的倍数(鸽笼原理)
    [51NOD1420] 数袋鼠好有趣(贪心)
    [CF808A] Lucky Year(规律)
    [CF808B] Average Sleep Time([强行]树状数组,数学)
    [CF808C] Tea Party(贪心)
    [CF808D] Array Division(暴力,枚举)
  • 原文地址:https://www.cnblogs.com/aicanxxx/p/6870244.html
Copyright © 2011-2022 走看看