摘要:为啥因为变量提升,变量的声明被提升到当前作用域的顶部了。也就是可以想象成这样此外,还有函数提升,和变量提升类似和被提升了,所以不会报错。开始处理函数声明,再次提醒,函数表达式不会被处理。
变量提升 & 函数提升
function f() { console.log(a); // ? var a = 1; } f();
简单简单,打印结果是 undefined。为啥?因为变量提升,变量 a 的声明被提升到当前作用域的顶部了。也就是可以想象成这样:
function f() { var a; console.log(a); a = 1; }
此外,还有函数提升,和变量提升类似:
f1(); f2(); function f1() { console.log(1) } function f2() { console.log(2) }
f1 和 f2 被提升了,所以不会报错。很多文章中都会经常提到提升这个概念。
但是,这是真的吗?JS 解释器在执行代码时还顺带帮你变动一下代码的顺序?想想也觉得不太可能嘛~那么到底是咋回事捏?
JavaScript 的可执行代码(executable code)有三种,分别为 全局代码、函数代码以及 eval 代码。鉴于 eval 不被推荐使用,咱就不理它。
执行上下文当执行全局代码时,会创建一个全局执行上下文。当执行一个函数的时候,会创建一个函数执行上下文。全局执行上下文只会有一个,而函数执行上下文可以有很多很多个。JS 引擎会创建 执行上下文栈(execution context stack, ECS)去管理这些执行上下文。我们以函数执行上下文为例。
变量对象对于一个执行上下文而言,可以抽象化为这样一个对象:
contextObject = { VariableObject, ScopeChain, thisValue }
其中变量对象(Variable Object,VO)是包含与当前执行上下文相关的数据作用域,它存储着当前上下文中定义的变量声明、函数声明(注意:不包含函数表达式),另外函数执行上下文中还会有参数。
变量对象是一个抽象概念,在不同的执行上下文中会以不同的对象呈现。比如在全局上下文中,变量对象就是全局对象本身(这也是为什么我们能够通过全局对象的属性名称引用全局变量)。而对于函数执行上下文,是用活动对象(Activation Object,AO)来表示变量对象的,我们访问的便是活动对象上的属性。
执行过程执行上下文中的代码会被分出两个阶段进行处理,分别为:
进入执行上下文,即分析创建阶段
执行代码,即执行阶段
分析创建阶段此时进入执行上下文,但在开始执行代码之前。此时的变量对象:
如果是函数上下文,那么会通过函数的 arguments 属性初始化,并处理形参。此时变量对象中会包含一个 arguments 对象,以及所有的形参,并且会检查是否有与之对应的实参,有则值初始化为实参的值,没有的话就设为 undefined。
处理函数声明。开始处理函数声明,再次提醒,函数表达式不会被处理。此时会检查变量对象中是否已经有同名属性,如果有,则替换它,如果没有则创建属性,值初始化为对应的函数对象。
处理变量声明。与处理函数声明不同的是,如果变量对象中存在有同名属性,此时会跳过该变量不做任何事,即不会产生覆盖,如果没有则创建属性并初始化为 undefined。
了解了第一个阶段,那么来看个例子:
function f(a, b) { var c = 3; function d() {}; var e = function() {}; (function f() {}); } f(1, 2);
当执行 f 函数时,进入执行上下文,我们可以描述出此时的 AO:
AO = { arguments: { 0: 1, 1: 2, length: 2 }, a: 1, b: 2, c: undefined, d: reference to function() {}, e: undefined, } // 注意:(function f() {}) 为函数表达式,不会被处理执行阶段
执行阶段就相对简单,代码顺序执行,并且会根据代码去修改变量对象中的值,所以当函数执行完后的 AO 会是:
AO = { arguments: { 0: 1, 1: 2, length: 2 }, a: 1, b: 2, c: 3, d: reference to function() {}, e: reference to function() {}, }总结
综上所述:
全局上下文的变量对象即是全局对象;
函数上下文的变量对象会以 arguments 对象初始化;
在第一个阶段会依次给变量对象添加形参(函数上下文)、函数声明、变量声明;
在第二个阶段,会修改变量对象中的属性值。
思考题console.log(f); // ? function f() {} var f = 1;
打印结果会是一个函数对象。因为第一个阶段中先处理函数声明后处理变量声明,当处理变量声明时变量对象中已经存在同名属性 f,不会做任何事直接跳过,所以 f 的值会是一个函数。
你也可以多找几道题尝试分析以加深理解。当然,日常工作中还是直接想成提升来得方便些,只是希望通过本文能让你了解到一些背后的事情。最后,如果文中有任何错误或不足之处,还请指出~
文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。
转载请注明本文地址:https://www.ucloud.cn/yun/104225.html
摘要:浏览器的主要组成包括有调用堆栈,事件循环,任务队列和。好了,现在有了前面这些知识,我们可以看一下这道题的讲解过程实现步骤调用会将函数放入调用堆栈。由于调用堆栈是空的,事件循环将选择回调并将其推入调用堆栈进行处理。进程再次重复,堆栈不会溢出。 JavaScript是前端开发中非常重要的一门语言,浏览器是他主要运行的地方。JavaScript是一个非常有意思的语言,但是他有很多一些概念,大...
摘要:中国的是一个阴谋让我们首先回到的初衷。春阳曾经分享过的藏宝图报告里有过一个关于家厂商毛利水平的统计,如下图所示,其中位数是。每一年,都会有人问我,春阳,你觉得SaaS行业到时候了吗?每一年,都会有媒体发文,SaaS已来,未来可期....是的,每一年...行业的媒体人喜欢给SaaS灌鸡汤是没有毛病的,本身这就是个留不住人才、熬不出日子的行业,如果我们再看衰它,媒体本身也是活不下去了…对这个问题...
摘要:类就是产生各种不同类型事件的产出器,比如定时器事件读写事件等等,为了提升民族荣誉感,我们将这些各种事件比作各种战斗机比如歼歼和歼。类就相对容易介入了,这玩意显然就是一个航空母舰了,为了提升民族荣誉感,我们就把类当作是辽宁舰。 [原文地址:https://blog.ti-node.com/blog...] 实际上php.net上是有event扩展的使用说明手册,但是呢,对于初学者来说却并...
阅读 1668·2021-11-19 09:40
阅读 2923·2021-09-24 10:27
阅读 3214·2021-09-02 15:15
阅读 1876·2019-08-30 15:54
阅读 1201·2019-08-30 15:54
阅读 1368·2019-08-30 13:12
阅读 625·2019-08-28 18:05
阅读 2793·2019-08-27 10:53