文章部分实例和内容来自鸟哥的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’.