Javascript 运行上下文和作用域链

时间:2022-06-29 20:06:22

一、作用域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的某个元素中找到或者无法找到该变量。

Javascript 运行上下文和作用域链的更多相关文章

  1. JavaScript - 运行机制,作用域,作用域链(Scope chain)

    参考 https://www.jianshu.com/p/3b5f0cb59344 https://jingyan.baidu.com/article/4f34706e18745be386b56d46 ...

  2. 深度剖析Javascript执行环境、作用域链

    一.执行环境 执行环境(也叫做执行上下文,Execution Context)是Javascript中最为重要的一个概念.执行环境定义了变量或函数有权访问其他数据,决定了它们各自的行为.每个执行环境都 ...

  3. JavaScript高级程序设计之作用域链

    JavaScript只有函数作用域:每个函数都有个作用域链直达window对象. 变量的查找由内而外层层查找,找到即止. 同时不仅可以查找使用,甚至可以改变外部变量. var color = &quo ...

  4. Javascript中闭包的作用域链

    作用域定义了在当前上下文中能够被访问到的成员,在Javascript中分为全局作用域和函数作用域,通过函数嵌套可以实现嵌套作用域. 闭包一般发生在嵌套作用域中.闭包是JavaScript最强大的特性之 ...

  5. javascript痛点之二作用域链

    1.执行环境(执行上下文) 先看段代码 var a = 10; var b = 20; function cc(){ var c = 30; alert("b="+b); } cc ...

  6. javaScript执行环境、作用域链与闭包

    一.执行环境 执行环境定义了变量和函数有权访问的其他数据,决定了他们各自的行为:每个执行环境都有一个与之关联的变量对象,环境中定义的所有变量和函数都保存在这个对象中.虽然我们编写的代码无法访问这个对象 ...

  7. javascript深入理解-从作用域链理解闭包

    一.概要 红宝书(P178)对于闭包的定义:闭包就是有权访问另外一个函数作用域中变量的函数. MDN,对于闭包的定义:闭包就是指能够访问*变量的函数. 那么什么是*变量?*变量就是在函数中使用, ...

  8. 【进阶2-2期】JavaScript深入之从作用域链理解闭包(转)

    这是我在公众号(高级前端进阶)看到的文章,现在做笔记   https://github.com/yygmind/blog/issues/18 红宝书(p178)上对于闭包的定义:闭包是指有权访问另外一 ...

  9. javascript 执行环境,作用域链和闭包

    首先看下这条语句: (function($) {…})(jQuery): 1.原理: function(arg){…}这就定义了一个匿名函数,参数为arg 而调用函数时,是在函数后面写上括号和实参的, ...

随机推荐

  1. 正则表达式解析url参数

    解析url参数正则:(?<=\?|&)[\w\={}\\\\,-:'\s'""]*(?=[^#\s]|) 意思是(?<=\?|&) 从?或&符号 ...

  2. js中属性和方法的类型和区别

    对象的属性:私有属性(var).类属性(静态属性).对象属性(this).原型属性(prototype). 对象的方法: 私有方法(funtion).类方法(静态方法).对象方法(this).原型方法 ...

  3. 修改tomcat的端口

  4. 访问google,youtube

    一.找到host文件 windows : C:\windows\system32\drivers\etc mac os: /private/etc linux : /etc 二.修改host文件 ht ...

  5. 01JavaIO详解&lowbar;File类

    对程序语言设计者来说,设计一个令人满意的I/O系统,是件极艰难的任务.——摘自Think in java 对java而言,File表示的是文件或目录.但是我们知道文件和目录是不一样的,文件里面存放的是 ...

  6. winform学习之----进程和线程

    Process[] pros = Process.GetProcesses();//获取多个进程            foreach(var item in pros)            {   ...

  7. 在ASP&period;NET MVC中使用DropDownList

    在ASP.NET MVC中,尽管我们可以直接在页面中编写HTML控件,并绑定控件的属性,但更方便的办法还是使用HtmlHelper中的辅助方法.在View中,包含一个类型为HtmlHelper的属性H ...

  8. 蓝桥杯 C语言 基础训练 数列排序

    问题描述 给定一个长度为n的数列,将这个数列按从小到大的顺序排列.1<=n<=200 输入格式 第一行为一个整数n. 第二行包含n个整数,为待排序的数,每个整数的绝对值小于10000. 输 ...

  9. java继承(一)

    虽然说java中的面向对象的概念不多,但是具体的细节还是值得大家学习研究,java中的继承实际上就是子类拥有父类所有的内容(除私有信息外),并对其进行扩展.下面是我的笔记,主要包含以下一些内容点: 构 ...

  10. 关于Spring的HibernateTemplate的findByExample方法使用时的一点注意。

    此前我们已经介绍了HibernateTemplate的使用配置方法,但是对其使用没有仔细说明.因为最近比较忙,我先不去介绍,而是重点说明一下容易引起问题的findByExample方法. 我尝试反编译 ...