Javascript 运行上下文和作用域链

一、作用域Scope和上下文Context

    在javascript中,作用域scope和上下文context是两个不同的概念。每个函数调用都会伴随着scope和context,从本质上来说,scope是和函数绑定的,而context是基于对象的。即scope用于在函数调用时提供变量访问,且每次函数调用时,都不同;而context始终是关键词this的值,它指向当前执行代码所属的对象。
scope 作用域
    在前一篇的“javascript变量”部分讨论了javascript的作用域,分为全局和局部,且javascript中不存在块作用域。

** 'this' context 上下文**
    context 经常被函数所调用的方式所决定。(1)当函数被作为一个对象的方法调用时,this 被设置为该函数所属的对象。如

var obj = {
    foo: function() {
        return this;   
    }
};
obj.foo() === obj; // true。 this指向obj对象

(2)当使用new关键字去创建一个新的函数对象时,this的值也被设置为新创建的函数对象。比如

function foo() {
    alert(this);
}
foo() // window
new foo() // foo

(3)当函数被普通调用时,this被为全局contex或者浏览器的window对象。比如

function foo() {
    alert(this);
}
foo() // window

二、函数生命周期

    函数生命周期可以分为创建和执行两个阶段。
    在函数创建阶段,JS解析引擎进行预解析,会将函数声明提前,同时将该函数放到全局作用域中或当前函数的上一级函数的局部作用域中。
    在函数执行阶段,JS解析引擎会将当前函数的局部变量和内部函数进行声明提前,然后再执行业务代码,当函数执行完退出时,释放该函数的执行上下文,并注销该函数的局部变量。

三、变量对象

VO 和 AO
    VO (Variable Object)变量对象,对应的是函数创建阶段,JS解析引擎进行预解析时,所有变量和函数的声明(即在JS引擎的预解析阶段,就确定了VO的内容,只不过此时大部分属性的值都是undefined)。VO与执行上下文相关,知道自己的数据存储在哪里,并且知道如何访问。VO是一个与执行上下文相关的特殊对象,它存储着在上下文中声明的以下内容:
(1)变量 (var, 变量声明);
(2)函数声明 (FunctionDeclaration, 缩写为FD);
(3)函数的形参

function add(a,b){
    var sum = a + b;
    function say(){
        alert(sum);
    }
    return sum;
}
// sum,say,a,b 组合的对象就是VO,不过该对象的值基本上都是undefined

    AO(Activation Object)对应的是函数执行阶段,当函数被调用执行时,会创建一个执行上下文,该执行上下文包含了函数所需的所有变量,该变量共同组成了一个新的对象就是Activation Object。该对象包括了:
(1)函数的所有局部变量
(2)函数的所有命名参数声明(Function Declaration)
(3)函数的参数集合

function add(a,b){
    var sum = a + b;
         var x = 10;
    function say(){
        alert(sum);
    }
    return sum;
}
add(4,5);
//  AO = {
//      arguments : [4,5],
//      a : 4,
//      b : 5,
//          x: undefined
//      say : <reference to function>,
//      sum : undefined
//  }

更详细的关于变量对象VO的知识,请访问:http://www.cnblogs.com/TomXu/archive/2012/01/16/2309728.html

四、执行上下文

    执行上下文(execution context)是ECMAScript规范中用来描述 JavaScript 代码执行的抽象概念。所有的 JavaScript 代码都是在某个执行上下文中运行的。在当前执行上下文中调用 function会进入一个新的执行上下文。该function调用结束后会返回到原来的执行上下文中。如果function在调用过程中抛出异常,并且没有将其捕获,有可能从多个执行上下文中退出。在function调用过程中,也可能调用其他的function,从而进入新的执行上下文,由此形成一个执行上下文栈。

    每个执行上下文都与一个作用域链(scope chain)关联起来。该作用域链用来在function执行时求出标识符(identifier)的值。该链中包含多个对象,在对标识符进行求值的过程中,会从链首的对象开始,然后依次查找后面的对象,直到在某个对象中找到与标识符名称相同的属性。在每个对象中进行属性查找时,会使用该对象的prototype链。在一个执行上下文中,与其关联的作用域链只会被with语句和catch 子句影响。

执行上下文属性
    每个执行上下文都有三个重要的属性,变量对象(Variable Object), 作用域链(Scope Chain)和this,当然还有一些其他属性。
![][3]

    当一段javascript代码被执行的时候,javascript解释器会创建并使用Execution Context,这里有两个阶段:
(1)创建阶段(当函数被调用,但开始执行内部代码之前)
(a) 创建 Scope Chain
(b) 创建VO/AO (函数内部变量声明、函数声明、函数参数)
(c) 设置this值
(2)激活阶段/代码执行阶段
(a) 设置变量的值、函数的引用,然后解释/执行代码。

在阶段(1)(b)创建VO/AO这一步,解释器主要做了以下事情:
(1)根据函数的参数,创建并初始化参数列表
(2)扫描函数内部代码,查找函数声明。对于所有找到的内部函数声明,将函数名和函数引用存储 VO/AO中;如果 VO/AO中已经有同名的函数,那么就进行覆盖
(3)扫描函数内部代码,查找变量声明。对于所有找到的变量声明,将变量名存入VO/AO中,并初始化为 undefined;如果变量名称和已经声明的形式参数或函数相同,则变量声明不会干扰已经存在的这类属性(就是说变量无效)
比如以下代码:

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

    };
    function c() {

    }
}
foo(22);

在“创建阶段”,可以得到下面的 Execution Context object:

fooExecutionContext = {
    scopeChain: { ... },
    variableObject: {
        arguments: {
            0: 22,
            length: 1
        },
        i: 22,
        c: pointer to function c()
        a: undefined,
        b: undefined
    },
    this: { ... }
}

在“激活/代码执行阶段”,Execution Context object 被更新为:

fooExecutionContext = {
    scopeChain: { ... },
    variableObject: {
        arguments: {
            0: 22,
            length: 1
        },
        i: 22,
        c: pointer to function c()
        a: 'hello',
        b: pointer to function privateB()
    },
    this: { ... }
}

    函数在定义时就会确定它的作用域和作用域链(静态),只有在调用的时候才会创建一个执行上下文,(1)其中包含了调用时的形参,函数内的函数声明与变量,同时创建活动对象AO;(2)并将AO压入执行上下文的作用域链的最前端,执行上下文的作用域链是通过它正在调用的函数的[[scope]]属性得到的(动态);(3)执行上下文对象中也包含this的属性

五、作用域链 scope chain

    每个运行上下文都有自己的变量对象,对于全局上下文,它是全局对象本身;对于函数,它是活动对象。作用域链是运行上下文所有变量对象(包括父变量对象)的列表。此链表用于查询标识符。

var x = 10;
function foo() { 
  var y = 20; 
  function bar() {
    alert(x + y);
  } 
  return bar; 
}
foo()(); // 30

上面的例子中, bar 上下文的作用域链包括 AO(bar) --> AO(foo) -- > VO(global).

作用域链如何构造的
    上面提到,作用域链Scope Chain是执行上下文Execution Context的一个属性。它是在函数被执行时,通过被执行函数的[[scope]]属性得到。
    函数创建时:在javascript中,函数也是一个对象,它有一个属性[[scope]],该属性是在函数被创建时写入,它是该函数对象的所有父变量对象的层级链,它存在于函数这个对象中,直到函数销毁。
    函数执行时:创建执行上下文Execution context, 执行上下文Execution context 把 AO 放在 函数[[scope]]最前面作为该执行上下文的Scope chain。
即 Scope chain(运行上下文的属性,动态) = AO|VO(运行上下文的属性,动态) + [[Scope]](函数的属性,静态)

一个例子

var x = 10; 
function foo() {
  var y = 20; 
  function bar() {
    var z = 30;
    alert(x +  y + z);
  } 
  bar();
}
foo(); // 60

全局上下文的变量对象是:

globalContext.VO === Global = {
  x: 10
  foo: <reference to function>
};

在“foo”创建时,“foo”的[[scope]]属性是:

foo.[[Scope]] = [
  globalContext.VO
];

在“foo”激活时(进入上下文),“foo”上下文的活动对象是:

fooContext.AO = {
  y: 20,
  bar: <reference to function>
};

“foo”上下文的作用域链为:

fooContext.Scope = fooContext.AO + foo.[[Scope]] // i.e.: 
fooContext.Scope = [
  fooContext.AO,
  globalContext.VO
];

内部函数“bar”创建时,其[[scope]]为:

bar.[[Scope]] = [
  fooContext.AO,
  globalContext.VO
];

在“bar”激活时,“bar”上下文的活动对象为:

barContext.AO = {
  z: 30
};

“bar”上下文的作用域链为:

barContext.Scope = barContext.AO + bar.[[Scope]] // i.e.:
 
barContext.Scope = [
  barContext.AO,
  fooContext.AO,
  globalContext.VO
];

对“x”、“y”、“z”的标识符解析如下:

  • "x"
    -- barContext.AO // not found
    -- fooContext.AO // not found
    -- globalContext.VO // found - 10

  • "y"
    -- barContext.AO // not found
    -- fooContext.AO // found - 20

  • "z"
    -- barContext.AO // found - 30

基于作用域链的变量查询
    在代码执行时需要访问某个变量值时,JS引擎会在Execution context的scope chain中从头到尾查找,直到在scope chain的某个元素中找到或者无法找到该变量。

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

推荐阅读更多精彩内容