在JavaScript中,确定不可避免的须要声明变量和函数,JS编译器是如何找到这些变量的呢?javascript
咱们还得对执行上下文有一个进一步的了解。前端
在上一篇文章中已经知道,当调用一个函数时(激活),一个新的执行上下文就会被建立。一个执行上下文的生命周期能够分为两个阶段。java
•建立阶段面试
在这个阶段中,执行上下文会分别建立变量对象,创建做用域链,以及肯定this指向。浏览器
•代码执行阶段微信
建立完成以后,就会开始执行代码,会完成变量赋值,函数引用,以及执行其余代码。前端工程师
从这里能够看出详细了解执行上下文极为重要,由于其中涉及到了变量对象,做用域链,this等不少人没有怎么弄明白,可是却极为重要的概念,它关系到咱们能不能真正理解JavaScript。在后面的文章中咱们会一一详细总结,本文的核心是变量对象。app
变量对象(Variable Object)
变量对象的建立,依次经历了如下几个过程。异步
// 这里a为属性名,20是属性值{ a: 20}
1、创建arguments对象:检查当前上下文中的参数,创建该对象下的属性与 属性值。async
函数参数
2、检查当前上下文的函数声明,也就是使用function关键字声明的函数。在变量对象中以函数名创建一个属性,属性值为指向该函数所在内存地址的引用
3、检查当前上下文中的变量声明,每找到一个变量声明,就在变量对象中以变量名创建一个属性,属性值为undefined
若是变量与函数同名,则在这个阶段,以函数值为准
console.log(foo); // function foofunction foo() { console.log('function foo') }var foo = 20;
// 上栗的执行顺序为
// 首先将全部函数声明放入变量对象中function foo() { console.log('function foo') }
// 其次将全部变量声明放入变量对象中,可是由于foo已经存在同名函数,此时以函数值为准,而不会被undefined覆盖// var foo = undefined;
// 而后开始执行阶段代码的执行console.log(foo); // function foofoo = 20;
根据这个规则,理解变量提高就变得十分简单了。在不少文章中虽然提到了变量提高,可是具体是怎么回事还真的不少人都说不出来,之后在面试中用变量对象的建立过程跟面试官解释变量提高,简直逼格满满。
在上面的规则中咱们看出,function声明会比var声明优先级更高一点。为了帮助你们更好的理解变量对象,咱们结合一些简单的例子来进行探讨。
// demo01function 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 ObjectAO = { 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();
再来一个例子,巩固一下咱们的理解。
// demo2function 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 -> AOVO = { arguments: {...}, foo: 'Hello', bar: <bar reference>, this: Window}
须要结合上面的知识,仔细对比这个例子中变量对象从建立阶段到执行阶段的变化,若是你已经理解了,说明变量对象相关的东西都已经难不倒你了。
全局上下文的变量对象
以浏览器中为例,全局对象为window。全局上下文有一个特殊的地方,它的变量对象,就是window对象。而这个特殊,在this指向上也一样适用,this也是指向window。
// 以浏览器中为例,全局对象为window// 全局上下文windowEC = { VO: Window, scopeChain: {}, this: Window}
除此以外,全局上下文的生命周期,与程序的生命周期一致,只要程序运行不结束,好比关掉浏览器窗口,全局上下文就会一直存在。其余全部的上下文环境,都能直接访问全局上下文的属性。
let/const
ES6中,新增了使用let/const来声明变量。我想他们的使用确定难不倒你们。但是有一个问题不知道你们思考过没有,let/const声明的变量,是否还会变量提高?
是的,这个刁钻的问题也成为了各大面试官爱问的细节。很贱!可也没办法,仍是要弄明白怎么回事!
咱们来作个试验,验证一下这个问题:
第一步,咱们直接使用一个未定义的变量
console.log(a);
报错信息以下:
第二步,咱们在let以前调用变量
console.log(a);let a = 10;
会发生什么?会打印出undefined吗?
看看结果
不能在初始化以前访问a。
这个报错说明了什么问题呢?变量定义了,可是没有初始化。
因此在这里咱们就能够得出结论:let/const声明的变量,仍然会提早被收集到变量对象中,但和var不一样的是,let/const定义的变量,不会在这个时候给他赋值undefined。
由于彻底没有赋值,即便变量提高了,咱们也不能在赋值以前调用他。这就是咱们常说的暂时性死区。
最后,变量提高的现象确实会对咱们的代码形成一些负面影响,所以,开发中的好习惯,就是尽可能将变量声明放在最前面来写。
❤️爱心三连击
1.看到这里了就点个在看支持下吧,你的「在看」是我创做的动力。
2.关注公众号图雀社区
,「带你一块儿学优质实战技术教程」!
3.特殊阶段,带好口罩,作好我的防御。
4.添加微信【little-tuture】,拉你进技术交流群一块儿学习。
·END·
汇聚精彩的免费实战教程
喜欢本文,点个“在看”告诉我

本文分享自微信公众号 - 图雀社区(tuture-dev)。
若有侵权,请联系 support@oschina.cn 删除。
本文参与“OSC源创计划”,欢迎正在阅读的你也加入,一块儿分享。