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的某个元素中找到或者无法找到该变量。
Javascript 运行上下文和作用域链的更多相关文章
- JavaScript - 运行机制,作用域,作用域链(Scope chain)
参考 https://www.jianshu.com/p/3b5f0cb59344 https://jingyan.baidu.com/article/4f34706e18745be386b56d46 ...
- 深度剖析Javascript执行环境、作用域链
一.执行环境 执行环境(也叫做执行上下文,Execution Context)是Javascript中最为重要的一个概念.执行环境定义了变量或函数有权访问其他数据,决定了它们各自的行为.每个执行环境都 ...
- JavaScript高级程序设计之作用域链
JavaScript只有函数作用域:每个函数都有个作用域链直达window对象. 变量的查找由内而外层层查找,找到即止. 同时不仅可以查找使用,甚至可以改变外部变量. var color = &quo ...
- Javascript中闭包的作用域链
作用域定义了在当前上下文中能够被访问到的成员,在Javascript中分为全局作用域和函数作用域,通过函数嵌套可以实现嵌套作用域. 闭包一般发生在嵌套作用域中.闭包是JavaScript最强大的特性之 ...
- javascript痛点之二作用域链
1.执行环境(执行上下文) 先看段代码 var a = 10; var b = 20; function cc(){ var c = 30; alert("b="+b); } cc ...
- javaScript执行环境、作用域链与闭包
一.执行环境 执行环境定义了变量和函数有权访问的其他数据,决定了他们各自的行为:每个执行环境都有一个与之关联的变量对象,环境中定义的所有变量和函数都保存在这个对象中.虽然我们编写的代码无法访问这个对象 ...
- javascript深入理解-从作用域链理解闭包
一.概要 红宝书(P178)对于闭包的定义:闭包就是有权访问另外一个函数作用域中变量的函数. MDN,对于闭包的定义:闭包就是指能够访问自由变量的函数. 那么什么是自由变量?自由变量就是在函数中使用, ...
- 【进阶2-2期】JavaScript深入之从作用域链理解闭包(转)
这是我在公众号(高级前端进阶)看到的文章,现在做笔记 https://github.com/yygmind/blog/issues/18 红宝书(p178)上对于闭包的定义:闭包是指有权访问另外一 ...
- javascript 执行环境,作用域链和闭包
首先看下这条语句: (function($) {…})(jQuery): 1.原理: function(arg){…}这就定义了一个匿名函数,参数为arg 而调用函数时,是在函数后面写上括号和实参的, ...
随机推荐
- 关于EL表达式的学习总结
一.EL表达式简介 EL 全名为Expression Language.EL主要作用: 1.获取数据 EL表达式主要用于替换JSP页面中的脚本表达式,以从各种类型的web域 中检索java对象.获取数 ...
- uestc 猛男搜索26题 by qscqesze
https://vjudge.net/contest/202940#overview 不断更新
- Java中Double类型的精确计算
import java.math.BigDecimal; public class DoubleUtil { private static final int DEF_DIV_SCALE = 5; / ...
- JS膏集02
JS膏集02 1.复习 函数也是对象 2.贪食蛇案例 <!DOCTYPE html> <html lang="en"> <head> <m ...
- web项目上传图片需要刷新文件夹才能显示
问题:上传图片到指定的目录,比如上传到项目文件夹中E:\briup\workspace\Buddhism\WebContent\userImage,但是上传呢完毕之后需要手动刷新该文件夹,网页上才能正 ...
- JSP(3)—Cookie和Session
HTTP是一个无状态的协议,web服务器无法分辨出那些请求是同一个浏览器发出的,浏览器每一次请求都是孤立的 即使HTTP1.1支持持续链接,但当用户有一段时间没有请求时,连接也会关闭. 如何实现网上的 ...
- pygame-KidsCanCode系列jumpy-part11-角色动画(下)
接上节继续,上节并没有处理向左走.向右走的动画效果,这节补上,看似很简单,但是有一些细节还是要注意: def jump(self): hits = pg.sprite.spritecollide(se ...
- 如何在MyBatis中优雅的使用枚举
问题 在编码过程中,经常会遇到用某个数值来表示某种状态.类型或者阶段的情况,比如有这样一个枚举: public enum ComputerState { OPEN(10), //开启 CLOSE( ...
- [Web 前端] 我不再使用React.setState的3个原因
copy from : https://blog.csdn.net/smk108/article/details/85237838 从几个月前开始,我在新开发的React组件中不再使用setState ...
- FFM及DeepFFM模型在推荐系统的探索及实践
12月20日至23日,全球人工智能与机器学习技术大会 AiCon 2018 在北京国际会议中心盛大举行,新浪微博AI Lab 的资深算法专家 张俊林@张俊林say 主持了大会的 搜索推荐与算法专题,并 ...