执行上下文和函数作用域小结

之前第一遍看javascript高级程序设计教程,看到函数作用域的概念,感觉终于可以解释之前程序中出现的种种关于闭包,作用域的奇怪现象了,这个假象一直维持到最近。

you don't know js。
no,I think i know.
but the truth is: I really don't know

起源问题

对我自己的困惑来自于这个问题:

var a = 10;
function bar(){
   console.log(a);
}
function foo(){
  var a=5;
  bar();
}
foo();  //10

bar作为内部函数,可以访问到外部变量a,也就是foo中定义的a,所以应该输出5。然而结果是10,道理很简单,只是当时的我混淆了两个概念,执行上下文和函数作用域。(个人认为js高级程序设计中这两个概念就混的很厉害)。

作用域的概念

es6之前,没有块级作用域,只有函数作用域和全局作用域。这里只讨论es6之前的。函数的作用域在函数定义的时候就已经决定了,而不是在执行的时候决定。也就是说,上面的bar函数在定义的时候,它的作用域就已经确定了,在该函数内部能够访问到的变量就是它自己定义的变量和上一层作用域中的变量。

执行上下文

js代码都是运行在执行上下文中的,最开始,程序直接进入全局的执行上下文(也就是window,或者node里面的global),如果碰到函数function1的执行(不是定义),就进入函数的执行上下文。在函数执行之前,要做以下几件事来决定函数对变量的访问权。

  1. 一个指针指向function1的作用域链,作用域链中的变量都可以进行访问,优先级由内至外。
  2. 创建一个variable object,也就是变量对象。这个变量里面的属性存储遵循下面几个原则:
    1. 先给变量对象建立一个arguments对象,如果函数调用时传入了变量,则初始化为那个变量,如果变量未传入,则初始化为undefined,如果是函数,则传递一个指向函数的指针,同时arguments还具有callee,length等属性也一并创建好
    2. 扫描当前函数里面的声明语句,如果找到了函数声明语句,就给变量对象中增加一个以函数名为属性名,指向函数的指针作为属性值。如果有重复的,直接属性值替换。
    3. 如果找到了变量声明语句,判断变量名是否已经存在在变量对象中的属性,则直接忽略,如果不存在,就新增一个属性,初始化为undefined
  3. 确定this的指向

关于变量对象是否会合并到自己的作用域链上,js高级程序设计中是将变量对象作为最高级的作用域,外层作为下一个作用域进而构成一个作用域链,我认为是否合并不重要,重要的是变量对象的优先级最高

当上述行为完成之后,才开始执行代码。能够访问到上述出现的所有代码,变量对象中的优先级最高,函数作用域链上越近优先级越高。
举个例子更实在一点:

function foo(i) {
    var a = 'hello';
    var b = function privateB() {

    };
    function c() {

    }
}

foo(22);

上述代码执行前,会做上述的3件事,之后,它的执行上下文对应的一个变量看起来是这样子:

fooExecutionContext = {
    scopeChain: { 全局变量们,foo },
    variableObject: {
        arguments: {
            0: 22,
            length: 1
        },
        i: 22,
        a: undefined,
        b: undefined
        c: pointer to function c()
    },
    this: { window(浏览器) or undefined(node)}
}

执行上下文是存储在栈的,也就是说,当前上下文执行过程中,遇到新的函数要执行, 就将新的执行上下文放入栈顶,决定了新的函数的执行上下文,执行结束之后出站,回复之前的执行上下文,从而在之前的执行上下文中继续执行。举个例子:

var a=10;
function   bar(){
    console.log(a)//undefined
    var a=5;
    function foo(){
       a=3;
       console.log(a);//3
    }
    foo();
    console.log(a);//3
}
bar();
console.log(a);//10

当前函数开始运行,构建全局执行上下文,遇到bar(),要开启一个新的上下文并进行堆栈,执行上述的3件事之后,新的上下文看起来像这样:

barFunctionContext{
    scopechain{全局的:可以访问到bar,a}
    variable object{
      arguments:{
          length:0,
          callee:(bar)
      },
      a:undefined,
      foo: 指向函数的指针(此时foo输出就是该函数),
    }
    this: window in browser or undefined in node
}

所以在执行的过程中,console.log(a)就会从当前的执行上下文中寻找a,进而输出undefined,而遇到a=5的时候,改变执行上下文中的a为5,当遇到foo()时,新建一个执行上下文,经过上面三件事之后,新的执行上下文大概像这样:

fooFunctionContext{
    scopechain{bar的执行上下文(a已经修改成5,别的不变)}
    variable object{
      arguments:{
          length:0,
          callee:(foo)
      },
    }
    this: window in browser or undefined in node
}

所以修改a的时候会从当前变量对象中查找,没有,就去作用域链中查找,在bar中的执行上下文中发现了a,修改a 的数值,然后输出3.接下来执行上下文出站,继续在bar的上下文中执行,console.log(a),已经被修改,输出3.bar执行上下文出站,执行console.log(a),此时是全局的a,也就是10。

总结

  1. 执行上下文采取堆栈的形式,这样才可以递归,函数嵌套而不会混乱
  2. 执行上下文的构建基本采用上述的3件事,之后才会去执行代码,这也是变量名提升的根本原因
  3. 理解了执行上下文,才能对js的一些复杂功能,闭包,柯里化(函数嵌套)真正理解

上述的文字大部分是看了下面的英文文献之后自己理解的,英文文献中还有很多的辅助图可以参考。理解的不到位的地方还请指出。

参考文章

  1. What is the Execution Context & Stack in JavaScript?
  2. ECMA-262-3 in detail. Chapter 2. Variable object.
  3. Javascript Closures
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,456评论 5 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,370评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,337评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,583评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,596评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,572评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,936评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,595评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,850评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,601评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,685评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,371评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,951评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,934评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,167评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,636评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,411评论 2 342

推荐阅读更多精彩内容