面对各种各样的JavaScript代码,我们有时候难免会犯错。可当自己仔细研究一下,哦原来是这么回事。有时候怎么会想为什么Javascript程序会是这样执行的呢?为什么没有得到自己预期的答案呢?自己到底是哪一步想错了呢?这时候就想如果我是JS执行流我会怎么执行?仔细想想这些问题,原来这其中还包含着很多自己不熟悉的知识点,今天就来总结下,同时也好加深印象。
首先我们来熟悉几个名称:
- 执行上下文栈(Execution Context Stack)
每一种代码的执行都需要依赖自身的上下文。函数的每一次调用,都会进入函数执行中的上下文,并且来计算函数中变量等的值。一个执行上下文可以激活另一个上下文,就好比一个函数调用了另一个函数(或者全局的上下文调用了一个全局函数),然后一层一层调用下去。逻辑上来说,这种实现方式是栈,我们可以称之为上下文堆栈。
激活其它上下文的某个上下文被称为 调用者(caller) 。被激活的上下文被称为被调用者(callee) 。当一个caller激活了一个callee,那么这个caller就会暂停它自身的执行,然后将控制权交给这个callee. 于是这个callee被放入堆栈,称为进行中的上下文[running/active execution context]. 当这个callee的上下文结束之后,会把控制权再次交给它的caller,然后caller会在刚才暂停的地方继续执行。在这个caller结束之后,会继续触发其他的上下文。一个callee可以用返回(return)或者抛出异常(exception)来结束自身的上下文。
当一段程序开始时,会先进入全局执行上下文环境[global execution context], 这个也是堆栈中最底部的元素。此全局程序会开始初始化,初始化生成必要的对象[objects]和函数[functions]. 在此全局上下文执行的过程中,它可能会激活一些方法(当然是已经初始化过的),然后进入他们的上下文环境,然后将新的元素压入堆栈。在这些初始化都结束之后,这个系统会等待一些事件(例如用户的鼠标点击等),会触发一些方法,然后进入一个新的上下文环境。ECMAScript运行时系统就是这样管理代码的执行。 - 执行上下文(Execution Context)
一个执行的上下文可以抽象的理解为object。每一个执行的上下文都有一系列的属性(变量对象(variable object),this指针(this value),作用域链(scope chain) ) - 变量对象(variable object)
变量对象是与执行上下文相关的 数据作用域(scope of data) 。它是与上下文关联的特殊对象,用于存储被定义在上下文中的变量(variables)和函数声明(function declarations) 。 - 活动对象(activation object)
在一个函数上下文中,变量对象被表示为活动对象。它包含变量(variables)和函数声明(function declarations) 普通参数(形参) 与特殊参数(arguments)对象(具有索引属性的参数映射表)。
知道了这几个名称后我们拿几个实例来分一下Javascript到底是如何执行的?
if (!("a" in window)) {
var a = 1;
}
alert(a);
这里如果不加思考,想当然的认为如果window不包含属性a,就声明一个变量a,然后赋值为1。所以答案为1。
可是正确答案是undefined,再仔细想想为什么?如果你就是JS程序你会如何运行?
详细分析:
当一段程序开始时,会先进入全局执行上下文环境并初始化生成必要的对象,在全局执行上下文环境中申明了变量a,根据变量声明提升,此时该上下文环境中的变量对象VO = {a:undefined}(全局上下文中VO还包括其他对象如Math,Date等对此题不构成影响,暂不考虑)。全局上下文环境初始化好后开始执行代码,首先判断变量a是否属于window对象,我们知道变量a已经在window对象中,所以,if条件语句不会被执行,直接执行alert,
该题可简化为:
var a;
if (!("a" in window)) {
a = 1;
}
alert(a); //undefined
第二题
function a(x) {
return x * 2;
}
var a;
alert(a);
我们知道变量对象中及包括函数声明也包括变量申明,在此题中有同名的函数和变量,此时函数声明会覆盖变量声明。但不会覆盖已经赋值的同名变量声明。所以此题中最后结果是函数
第三题
function b(x, y, a) {
arguments[2] = 10;
alert(a);
}
b(1, 2, 3);
在此题中,b函数被激活会开辟一个新的内存栈并添加到执行上下文栈中,执行流进入b函数上下文,此时变量对象表示为活动对象
AO = {
x : 1,
y : 2,
a : 3,
arguments:{0:1,1:2,2:3}
}
Arguments对象是活动对象的一个属性,它包括如下属性:
- callee — 指向当前函数的引用
- length — 真正传递的参数个数
- properties-indexes (字符串类型的整数) 属性的值就是函数的参数值(按参数列表从左到右排列)。 properties-indexes内部元素的个数等于arguments.length. properties-indexes的值和实际传递进来的参数之间是共享的。这个共享其实不是真正的共享一个内存地址,而是2个不同的内存地址,使用JavaScript引擎来保证2个值是随时一样的.
所以当代码执行到arguments[2] = 10;时a也会变为10。
参考文献: