文章部分实例和内容来自鸟哥的blogJavascript作用域原理
前端基础进阶(三):变量对象详解 波同学
在JS中,作用域的概念和其他语言差不多,是JS中一个极为重要的概念。在每次调用一个函数的时候 ,就会进入一个函数内的作用域,当从函数返回以后,就返回调用前的作用域.
理解作用域,首先理解几个概念
变量对象:执行环境(execution context)定义所有的变量和函数都存在这个对象中。虽然我们编写的代码无法访问这个对象,但是解析器在处理数据时后台会使用它
var foo=10;
function func(){};
//因为是在全局作用域当中,so...
Global VO={
foo:10,
func:<function>
}
变量对象的创建经历以下几个过程:
- 建立arguments对象,检查上下文中的参数,建立该对象下的属性和属性值。
- 检查当前上下文的函数声明
- 检查上下文变量声明,每找到一个变量声明,就在变量对象中以变量名建立一个属性,属性值为undefined。如果该变量名的属性已经存在,为了防止同名的函数被修改为undefined,则会直接跳过,原属性值不会被修改。
由上可以看出function的优先级比var优先级高
活动对象:在调用func的时候, 会创建一个活动对象(假设为aObj, 由JS引擎预编译时刻创建, 后面会介绍),并创建arguments属性
function foo(x,y){
var z=30;
function bar(){};
}
foo(10,20);
//当执行到foo(10,20)时即会产生AO
Activation Object={
z:30,
x:10,
y:20,
bar:<function>,
arguments:{0:10,1:20,length:2}
}
注意到, 因为函数对象的[[scope]]属性是在定义一个函数的时候决定的, 而非调用的时候, 所以如下面的例子
var name = 'laruence';
function echo() {
alert(name);
}
function env() {
var name = 'eve';
echo();
}
env();
结果
laruence
可以看出一段函数被激活时有两个阶段,一个是创建阶段,一种执行阶段
1.生成变量对象 1.变量赋值
创建阶段 ==》2.建立作用域 =====》执行==》2.函数引用 ===》出栈 回收
3.确定this的指向 3.执行代码
看下面梨字
// demo01
function test() {
console.log(a);
console.log(foo());
var a = 1;
function foo() {
return 2;
}
}
test();
从上下文开始理解,全局作用域的运行test()
时,test()的执行上下文开始创建。
创建过程
testEC = {
// 变量对象
VO: {},
scopeChain: {},
this: {}
}
// 因为本文暂时不详细解释作用域链和this,所以把变量对象专门提出来说明
// VO 为 Variable Object的缩写,即变量对象
VO = {
arguments: {...}, //注:在浏览器的展示中,函数的参数可能并不是放在arguments对象中,这里为了方便理解,我做了这样的处理
foo: <foo reference> // 表示foo的地址引用
a: undefined
}
变量对象和活动对象是一种对象,只不过是处于执行上下文的不同生命周期。
// 执行阶段
VO -> AO // Active Object
AO = {
arguments: {...},
foo: <foo reference>,
a: 1
}
因此demo1变成了如下代码
function test() {
function foo() {
return 2;
}
var a;
console.log(a);
console.log(foo());
a = 1;
}
test();
实际的例子:
function factory(){
var name="laruence";
var intro=function(){
console.log("I'm "+name);
}
return intro;
}
function app(para){
var name=para;
var func=factory();
func();
}
app("eve");
当调用app的时候, scope chain是由: {window活动对象(全局)}->{app的活动对象} 组成.
此时的scope chain如下:
[[scope chain]]=[
Active Object={
this:window,
arguments:{0:"eve",length:1}
name:'eve'
func:<function>
para:"eve",
},
Global VO={
this:window,
app:<function>,
window:<object>,
document:<object>
}
]
当调用进入factory的函数体的时候, 此时的factory的scope chain为:
[[scope chain]]={
Active Object={
this:window,
arguments:{},
name:"laruence",
intro:<function>,
},
Global Object(Variable Object)={
this:window,
factory:<function>,
app:<function>,
window:<object>,
document:<object>
}
}
在定义intro
函数的时候,intro
函数[[scope]]为:
[[scope chain]]={
Object={
name:"laruence",
intro:<function>,
this:<factory>, //注意这里的this指向
arguments:{}
},
Gloabal Object={
this:window,
factory:<function>,
document:<object>,
window:<object>
}
}
从factory
函数返回后,在app体内调用intro时,发生了标识符的解析,而此时的scope chain是:
[[scope chain]]={
intro AO={
<intro 活动对象>
} ,
Factory AO={
name:"laruence",
intro:<function>,
},
Global VO={
this:window,
factory:<function>,
document:<obeject>,
window:<object>
}
}
因为scope chain中,并不包含factory活动对象. 所以, name标识符解析的结果应该是factory活动对象中的name属性, 也就是’laruence’.