开年以后工做热情一直不是很高,这几天一直处于消极怠工状态。早上不想起床,起床了不想上班。明明放假以前工做热情还一直很高,一直心心念念的想把小程序项目怼出来,结果休假回来以后画风彻底不同了。我感受本身得了严重了节后综合征。还好撸了几篇文章,勉强表示这一周的时间没有彻底浪费。这篇文章要给你们介绍的是变量对象。javascript
在JavaScript中,咱们确定不可避免的须要声明变量和函数,但是JS解析器是如何找到这些变量的呢?咱们还得对执行上下文有一个进一步的了解。前端
在上一篇文章中,咱们已经知道,当调用一个函数时(激活),一个新的执行上下文就会被建立。而一个执行上下文的生命周期能够分为两个阶段。java
在这个阶段中,执行上下文会分别建立变量对象,创建做用域链,以及肯定this的指向。面试
建立完成以后,就会开始执行代码,这个时候,会完成变量赋值,函数引用,以及执行其余代码。小程序
从这里咱们就能够看出详细了解执行上下文极为重要,由于其中涉及到了变量对象,做用域链,this等不少人没有怎么弄明白,可是却极为重要的概念,它关系到咱们能不能真正理解JavaScript。在后面的文章中咱们会一一详细总结,这里咱们先重点了解变量对象。segmentfault
变量对象的建立,依次经历了如下几个过程。浏览器
许多读者在阅读到这的时候会由于下面的这样场景对于“跳过”一词产生疑问。既然变量声明的foo遇到函数声明的foo会跳过,但是为何最后foo的输出结果仍然是被覆盖了?函数
function foo() { console.log('function foo') } var foo = 20; console.log(foo); // 20
其实只是你们在阅读的时候不够仔细,由于上面的三条规则仅仅适用于变量对象的建立过程。也就是执行上下文的建立过程。而foo = 20
是在执行上下文的执行过程当中运行的,输出结果天然会是20。对比下例。this
console.log(foo); // function foo function foo() { console.log('function foo') } var foo = 20;
// 上栗的执行顺序为 // 首先将全部函数声明放入变量对象中 function foo() { console.log('function foo') } // 其次将全部变量声明放入变量对象中,可是由于foo已经存在同名函数,所以此时会跳过undefined的赋值 // var foo = undefined; // 而后开始执行阶段代码的执行 console.log(foo); // function foo foo = 20;
根据这个规则,理解变量提高就变得十分简单了。在不少文章中虽然提到了变量提高,可是具体是怎么回事还真的不少人都说不出来,之后在面试中用变量对象的建立过程跟面试官解释变量提高,保证瞬间提高逼格。spa
在上面的规则中咱们看出,function声明会比var声明优先级更高一点。为了帮助你们更好的理解变量对象,咱们结合一些简单的例子来进行探讨。
// demo01 function test() { console.log(a); console.log(foo()); var a = 1; function foo() { return 2; } } test();
在上例中,咱们直接从test()的执行上下文开始理解。全局做用域中运行test()
时,test()的执行上下文开始建立。为了便于理解,咱们用以下的形式来表示
// 建立过程 testEC = { // 变量对象 VO: {}, scopeChain: {} } // 由于本文暂时不详细解释做用域链,因此把变量对象专门提出来讲明 // VO 为 Variable Object的缩写,即变量对象 VO = { arguments: {...}, //注:在浏览器的展现中,函数的参数可能并非放在arguments对象中,这里为了方便理解,我作了这样的处理 foo: <foo reference> // 表示foo的地址引用 a: undefined }
未进入执行阶段以前,变量对象中的属性都不能访问!可是进入执行阶段以后,变量对象转变为了活动对象,里面的属性都能被访问了,而后开始进行执行阶段的操做。
这样,若是再面试的时候被问到变量对象和活动对象有什么区别,就又能够自如的应答了,他们其实都是同一个对象,只是处于执行上下文的不一样生命周期。不过只有处于函数调用栈栈顶的执行上下文中的变量对象,才会变成活动对象。
// 执行阶段 VO -> AO // Active Object AO = { arguments: {...}, foo: <foo reference>, a: 1, this: Window }
所以,上面的例子demo1,执行顺序就变成了这样
function test() { function foo() { return 2; } var a; console.log(a); console.log(foo()); a = 1; } test();
再来一个例子,巩固一下咱们的理解。
// demo2 function test() { console.log(foo); console.log(bar); var foo = 'Hello'; console.log(foo); var bar = function () { return 'world'; } function foo() { return 'hello'; } } test();
// 建立阶段 VO = { arguments: {...}, foo: <foo reference>, bar: undefined } // 这里有一个须要注意的地方,由于var声明的变量当遇到同名的属性时,会跳过而不会覆盖
// 执行阶段 VO -> AO VO = { arguments: {...}, foo: 'Hello', bar: <bar reference>, this: Window }
须要结合上面的知识,仔细对比这个例子中变量对象从建立阶段到执行阶段的变化,若是你已经理解了,说明变量对象相关的东西都已经难不倒你了。
以浏览器中为例,全局对象为window。
全局上下文有一个特殊的地方,它的变量对象,就是window对象。而这个特殊,在this指向上也一样适用,this也是指向window。
// 以浏览器中为例,全局对象为window // 全局上下文 windowEC = { VO: Window, scopeChain: {}, this: Window }
除此以外,全局上下文的生命周期,与程序的生命周期一致,只要程序运行不结束,好比关掉浏览器窗口,全局上下文就会一直存在。其余全部的上下文环境,都能直接访问全局上下文的属性。