前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >JavaScript 提升不完全指北

JavaScript 提升不完全指北

作者头像
撸码那些事
发布2019-08-06 10:35:05
4400
发布2019-08-06 10:35:05
举报
文章被收录于专栏:撸码那些事撸码那些事
我们直觉上会认为JavaScript 代码在执行时是由上到下一行一行执行的。但实际上这并不完全正确, 有一种特殊情况会导致这个假设是错误的,这种情况叫做提升。提升是指不管变量和函数声明在代码的哪个位置,都会提升到所在作用域的顶部

提升

思考下面的代码的执行结果:

代码语言:javascript
复制
a = 2;
var a;
console.log( a );

很多人会认为是 undefined , 因为 var a 声明在 a = 2 之后, 他们自然而然地认为变量被重新赋值了, 因此会被赋予默认值 undefined。但是, 真正的输出结果是 2。

再思考如下代码:

代码语言:javascript
复制
console.log( a );
var a = 2;

鉴于上一个代码片段所表现出来的某种非自上而下的行为特点, 你可能会认为这个代码片段也会有同样的行为而输出 2。还有人可能会认为, 由于变量 a 在使用前没有先进行声明,因此会抛出 ReferenceError 异常。但是很遗憾,两种猜测都是不对的,真正的输出结果是 undefined 。

为了搞清楚这个问题,我们需要回忆一下 JavaScript 作用域不完全指北 中提到的,JavaScript 是一门编译语言。当我们看到 var a = 2; 时, 可能会认为这是一个声明。但 JavaScript 实际上会将其看成两个声明:var a; 和 a = 2;。第一个定义声明是在编译阶段进行的。第二个赋值声明会被留在原地等待执行阶段

我们的第一段代码会被按照如下流程处理:

代码语言:javascript
复制
var a;

a = 2;
console.log( a ); //2

其中第一部分是编译, 而第二部分是执行。

同理,第二段代码会被按照如下流程处理:

代码语言:javascript
复制
var a;

console.log( a ); //undefined
a = 2;

这个过程就好像变量和函数声明从它们在代码中出现的位置被“移动”到了所在作用域的最上面。这个过程就叫作提升。

有几个需要特别注意的地方:

1.只是变量或者函数的声明被“移动”了,而赋值和其他的运行逻辑被留在了原地2.每个作用域都会进行提升操作

代码语言:javascript
复制
foo();
function foo() {
    console.log( a ); // undefined
    var a = 2;
}

被提升的不只是函数 foo(),还有函数 foo() 作用域中的变量 a 。处理后的代码流程如下:

代码语言:javascript
复制
function foo() {
  var a;
    console.log( a ); // undefined
    a = 2;
}
foo();

3.函数声明会被提升,但是函数表达式不会

代码语言:javascript
复制
foo(); // 不是 ReferenceError, 而是 TypeError!
var foo = function bar() {
    // ...
};

此处需要注意的是,运行 foo() 函数抛出的错误是 TypeError,而不是 ReferenceError。我们在作用域一文中讲到过这两种错误的区别,ReferenceError 是作用域判别失败,也就是嵌套的所有作用域中都不存在此标志符;而 TypeError 是作用域判别成功了,但是试图对这个变量的值做非法的操作,比如对一个非函数类型的值进行函数调用, 或着引用 null 或 undefined 类型的值中的属性。

示例代码中抛出 TypeError 错误就是因为对 undefined 做函数调用,根据这个能推断出实际上函数表达式也被提升了,只是在执行前没有被赋值。在这一点上,let 和 const 都是如此(这里不做探究,将会在后文中单独讲解),执行流程如下:

代码语言:javascript
复制
var foo;
foo(); // 不是 ReferenceError, 而是 TypeError!
foo = function bar() {
    // ...
};

4.函数优先,函数和变量都会被提升,但是函数会首先被提升,然后才是变量

思考如下代码:

代码语言:javascript
复制
foo(); // 1
var foo;
function foo() {
    console.log( 1 );
} 
foo = function() {
    console.log( 2 );
};

输出 1 而不是 2 ,引擎执行代码的实际流程如下:

代码语言:javascript
复制
function foo() {
    console.log( 1 );
} 
foo(); // 1
foo = function() {
    console.log( 2 );
};

var foo 尽管出现在 function foo()... 的声明之前, 但它是重复的声明,因此被忽略了, 因为函数声明会被提升到普通变量之前

尽管重复的 var 声明(没有赋值)会被忽略掉, 但出现在后面的函数声明还是可以覆盖前面的

代码语言:javascript
复制
foo(); // 3
function foo() {
console.log( 1 );
}
var foo = function() {
console.log( 2 );
};
function foo() {
console.log( 3 );
}

这样会带来一个严重的问题,一个普通块内部的函数声明通常会被提升到所在作用域的顶部,但是重复定义的函数,后定义的函数会覆盖先定义的函数,这会造成严重的代码问题。

代码语言:javascript
复制
foo(); // "b"
var a = true;
if (a) {
    function foo() { 
        console.log("a");
    }
}
else {
    function foo() {
        console.log("b"); 
    }
}

虽然 if 判断永远成立(因为 a = true),但是因为函数的提升和重复定义,会导致实际执行的永远是后定义的函数。本示例中的实际执行代码如下:

代码语言:javascript
复制
//function foo() { 
//    console.log("a");   //被覆盖
//}
function foo() {
    console.log("b"); 
}
foo(); // "b"

参考

•《你不知道的JavaScript》•《深入理解JavaScript特性》

-----END-----

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2019-08-02,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 CoderFocus 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 提升
  • 参考
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档