关于javascript中的 执行上下文和对象变量
什么是执行上下文
当浏览器的解释器开始执行我们的js代码的时候,js代码运行所处的环境可以被认为是代码的执行上下文,执行上下文(简称-EC)是ECMA-262标准里的一个抽象概念,用于同可执行代码(executable code)概念进行区分。一般来讲,执行上下文可以在以下三种情况产生:
1. 全局上下文(globalContext) 2. function 内部 3. Eval code.
看个例子,包含全局和function内部上下文
紫色框内表示全局的执行上下文,同时内部会有3个不同的Function context, function context可以有多个,但是全局上下文只有一个,并且当解释器开始执行代码的时候就会创建全局上下文并进入。 我们可以创建任意多个Function context,声明一个方法并执行的时候会自动创建该function context,同时创建一块区域,在该区域内创建的变量或其他声明不直接被外部context所访问。
执行上下文堆栈
浏览器内部js解释器是按照单线程的方式实现,意味着内部只能同时在做一件事情,其他的调用都会在被称为执行堆栈的地方排队。看下面的图:
当浏览器加载js的时候,就会默认进入全局上下文,如果全局代码中开始执行function,则会创建一个新的execution context,并把该execution context push到栈顶。
如果在function里面又调用内部function,则会执行相同的操作,创建新的execution context,并push到栈顶。看例子:
(function foo(i){
if(i === 3){
return;
}else{
foo(++i);
}
}
)(0)
foo会执行三次,每次执行会生成新的execution context,执行结束则自动出栈。
执行上下文的细节
现在我们知道伴随着function的调用,都会产生一个新的context,在解释器内部,大致分为两个阶段:
Stage I:创建阶段(function被调用,但是在开始执行任何代码之前)
创建阶段大致做了以下几件事情:
①:生成变量对象。该阶段把所有的声明都以key-value的形式提取出来,包括 函数的形参(value为实参的值),function arguments,内部function名(value是对内部function的引用),function内的变量声明(value值统一为undefined)。
②:创建作用域链。作用域链包含该上下文中的变量对象和所有父上下文的变量对象,用于变量查找)
③:给this赋值。关于this的理解,参考《对javascript this的理解》
Stage II:执行阶段(给变量赋值,逐步执行)
理解了以上两个执行阶段后,我们可以大体描绘一下执行上下文中有哪些东西,可以用一个带有3个属性的对象来表示:
executionContextObj = {
scopeChain: { /* variableObject + all parent execution context's variableObject */ },
variableObject: { /* function arguments / parameters, inner variable and function declarations */ },
this: {}
}
对象变量和活动对象 Variable/Activation Object[VO/AO]
executionContextObj在function每次被调用的时候创建,在上文提到的StageI阶段,解释器会对function进行扫描,包括function的arguments, 参数,内部变量声明和内部function声明,扫描的结果会被放到 我们称为 对象变量的对象中(variable object).
看例子:
var a = 10; function test(x) {
var b = 20;
}; test(30);
以上代码对应的变量对象应该为:
VO(global context) = {
a: 10,
test: <reference to function>
} VO(test function context) = {
x:30,
b:20
}
具体分为两种,
全局上下文中的对象变量
首先,我们要给全局对象一个明确的定义:
全局对象(Global object) 是在进入任何执行上下文之前就已经创建了的对象;
这个对象只存在一份,它的属性在程序中任何地方都可以访问,全局对象的生命周期终止于程序退出那一刻。
全局对象初始创建阶段将Math、String、Date、parseInt作为自身属性,等属性初始化,同样也可以有额外创建的其它对象作为属性(其可以指向到全局对象自身)。例如,在DOM中,全局对象的window属性就可以引用全局对象自身(当然,并不是所有的具体实现都是这样):
global = {
Math: <...>,
String: <...>
...
...
window: global //引用自身
};
当访问全局对象的属性时通常会忽略掉前缀,这是因为全局对象是不能通过名称直接访问的。不过我们依然可以通过全局上下文的this来访问全局对象,同样也可以递归引用自身。例如,DOM中的window。综上所述,代码可以简写为:
String(10); // 就是global.String(10); // 带有前缀
window.a = 10; // === global.window.a = 10 === global.a = 10;
this.b = 20; // global.b = 20;
因此,回到全局上下文中的变量对象——在这里,变量对象就是全局对象自己:
VO(globalContext) === global;
非常有必要要理解上述结论,基于这个原理,在全局上下文中声明的对应,我们才可以间接通过全局对象的属性来访问它(例如,事先不知道变量名称)。
var a = new String('test'); alert(a); // 直接访问,在VO(globalContext)里找到:"test" alert(window['a']); // 间接通过global访问:global === VO(globalContext): "test"
alert(a === this.a); // true var aKey = 'a';
alert(window[aKey]); // 间接通过动态属性名称访问:"test"
函数上下文中的变量对象
在函数执行上下文中,VO是不能直接访问的,此时由活动对象(activation object,缩写为AO)扮演VO的角色。
VO(functionContext) === AO;
活动对象是在进入函数上下文时刻被创建的,它通过函数的arguments属性初始化。arguments属性的值是Arguments对象:
AO = {
arguments: <ArgO>
};
Arguments对象是活动对象的一个属性,它包括如下属性:
- callee — 指向当前函数的引用
- length — 真正传递的参数个数
- properties-indexes (字符串类型的整数) 属性的值就是函数的参数值(按参数列表从左到右排列)。 properties-indexes内部元素的个数等于arguments.length. properties-indexes 的值和实际传递进来的参数之间是共享的。
例如:
function foo(x, y, z) { // 声明的函数参数数量arguments (x, y, z)
alert(foo.length); // 3 // 真正传进来的参数个数(only x, y)
alert(arguments.length); // 2 // 参数的callee是函数自身
alert(arguments.callee === foo); // true // 参数共享 alert(x === arguments[0]); // true
alert(x); // 10 arguments[0] = 20;
alert(x); // 20 x = 30;
alert(arguments[0]); // 30 // 不过,没有传进来的参数z,和参数的第3个索引值是不共享的 z = 40;
alert(arguments[2]); // undefined arguments[2] = 50;
alert(z); // 40 } foo(10, 20);
综上,来看一个比较综合的例子:
function foo(i) {
var a = 'hello';
var b = function privateB() { };
function c() { }
} foo(22);
foo被调用,StageI阶段:
foo executionContextObj = {
scopeChain: { ... },
varaible object :{
arguments:{
length:1,
callee : foo,//对调用foo的引用
0:22
}
i : 22,
c : <reference function()>,
a : undefined,
b : undefined
},
this : global }
Stage II :
fooExecutionContext = {
scopeChain: { ... },
variableObject: {
arguments: {
0: 22,
callee:foo
length: 1
},
i: 22,
c: pointer to function c()
a: 'hello',
b: pointer to function privateB()
},
this: global
}
关于javascript中的 执行上下文和对象变量的更多相关文章
- 【进阶1-1期】理解JavaScript 中的执行上下文和执行栈(转)
这是我在公众号(高级前端进阶)看到的文章,现在做笔记 https://mp.weixin.qq.com/s/tNl5B4uGdMkJ2bNdbbo82g 阅读笔记 执行上下文是当前 JavaScrip ...
- JavaScript 中的执行上下文和执行栈
JavaScript - 原理系列 在日常开发中,每当我们接手一个现有项目后,我们总喜欢先去看看别人写的代码.每当我们看到别人写出很酷的代码的时候,我们总会感慨!写出这么优美而又简洁的代码的兄弟到 ...
- JavaScript 中的执行上下文和调用栈是什么?
http://zcfy.cc/article/what-is-the-execution-context-amp-stack-in-javascript-by-david-shariff-4007.h ...
- JavaScript进阶之执行上下文和执行栈
js引擎的执行过程 执行上下文和执行栈属于js引擎的执行过程的预编译阶段. 执行上下文(Execution Context) 执行上下文是当前 JavaScript 代码被解析和执行时所在环境的抽象概 ...
- 【JS】JavaScript中的执行环境与作用域
JavaScript中的执行环境定义了变量或函数有权访问的数据(每个函数都有自己的执行环境),全局执行环境是最外围的执行环境,在浏览器中,全局执行环境就是window对象,所以所有的全局变量和函数都是 ...
- 前端学习 第二弹: JavaScript中的一些函数与对象(1)
前端学习 第二弹: JavaScript中的一些函数与对象(1) 1.apply与call函数 每个函数都包含两个非继承而来的方法:apply()和call(). 他们的用途相同,都是在特定的作用域中 ...
- javascript中var let const三种变量声明方式
javascript中var let const三种变量声明方式 1.var ①var表示声明了一个变量,并且可以同时初始化该变量. ②使用var语句声明的变量的作用域是当前执行位置的上下文:一个函 ...
- ES6新特性:Javascript中的Map和WeakMap对象
Map对象 Map对象是一种有对应 键/值 对的对象, JS的Object也是 键/值 对的对象 : ES6中Map相对于Object对象有几个区别: 1:Object对象有原型, 也就是说他有默认的 ...
- JavaScript中的两种全局对象
这里总结的东西特别适合先学习c/c++, Java这类标准语言再学JS的童鞋们看,因为JS在程序执行之前就会初始化一个全局对象,这个全局对象到底是什么是跟JS程序运行环境有关的. 根据JavaScri ...
随机推荐
- R command
setwd("D:/Research/code/rcode") #notice the "/" list.files(getwd()) heisenber ...
- 新手须知设计的法则 Mark
经常看到一些讲如何学习设计的文章,坦白讲感觉有些千篇一律.且不痛不痒,都说要看点书.学点画.练软件.多观察……唉,练软件这事还要说么,难道你还需要告诉一个人学开发是需要学习编程语言的? 学习是基于过往 ...
- hadoop2.6.0 --- 64位源代码
今天有朋友在群里找hadoop最新的2.6.0的源代码,其实这个源代码在hadoop的官方网站是有下载的(应该是32位的),还有一个src,不过给的是maven版本,需要自己在机器上编译一下(我的机器 ...
- Transact-SQL
Transact-SQL(又称T-SQL),是在Microsoft SQL Server和Sybase SQL Server上的ANSI SQL实现,与Oracle的PL/SQL性质相近(不只是实现A ...
- SharePoint咨询师之路:设计之前的那些事二:规模
提示:本系列只是一个学习笔记系列,大部分内容都可以从微软官方网站找到,本人只是按照自己的学习路径来学习和呈现这些知识. 有些内容是自己的经验和积 累,如果有不当之处,请指正. 咨询师更多的时候是解决方 ...
- UVALive 7077 - Song Jiang's rank list(模拟)
https://icpcarchive.ecs.baylor.edu/index.php?option=com_onlinejudge&Itemid=8&page=show_probl ...
- MSSQL手札三 MSSQL存储过程
--存储过程完成一段sql代码的封装 create proc trim --参数列表,多个间用逗号分隔 ) as --自定义代码段 ) set @str1=LTRIM(RTRIM(@str)) pri ...
- System.Data.SqlTypes.SqlNullValueException: 数据为空。不能对空值调用此方法或
有可能读出的数据为NULL,可以这样改: 方法一:while (reader.Read()){ for (int i = 0; i < 7; i++) { if (reader.IsDBNull ...
- Android Studio使用JNI
0x01 前言 本文讲述使用Android Studio通过静态注册.动态注册使用JNI的方法,以及加载第三方so文件的方法 0x02 Android Studio静态注册的方式使用JNI 1. 添加 ...
- UI:tomcat(说话小程序)、相框动画、UISgmentcontrol、UISwitch
UISegmentedControl 分段控件 //1. UISegmentedControl 分段控件 (就是一个一个的按钮) //分段显示的标题是以一个数组存储的 NSArray * titles ...