彻底理解变量与函数的声明提升

原文参考:《你不知道的 JavaScript》(上卷)第一部分 第四章。

变量声明

先来看两段代码,正确的运行结果已经给出,

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

直觉上会认为 JavaScript 代码在执行时是由上到下一行一行执行的,但实际上这并不完全正确。

**JS 引擎会在解释 JavaScript 代码之前首先对其进行编译。**编译阶段中的一部分工作就是找到所有的声明,并用合适的作用域将它们关联起来。

第一个代码片段会以如下形式进行处理:

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

第二个代码片段实际是按照以下流程处理的:

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

正确的思考思路是,

包括变量和函数在内的所有声明都会在任何代码被执行前首先被处理。

这意味着无论作用域中的声明出现在什么地方,都将在代码本身被执行前首先进行处理,每个作用域都会进行提升操作。

当你看到 var a = 2; 时,可能会认为这是一个声明

但实际上 JavaScript 引擎会将其看成:

var a;a = 2;,当作两个单独的声明

第一个定义声明是在编译阶段进行的。

第二个赋值声明则是执行阶段的任务。

函数声明会被提升,但是函数表达式却不会被提升。

函数声明

foo();
function foo() {
  console.log(a); // undefined
  var a = 2;
}

这段代码foo 函数的声明被提升了,因此第一行中的调用可以正常执行。

下面是处理流程。

function foo() {
  var a;
  console.log(a); // undefined
  a = 2;
}
foo();

函数表达式

foo(); // TypeError
bar(); // ReferenceError
var foo = function bar() {
  // ...
};

这段代码的变量标识符 foo 被提升并分配给所在作用域(在这里是全局作用域),

因此 foo() 不会导致 ReferenceError

但是 foo 此时并没有赋值(如果它是一个函数声明而不是函数表达式,那么就会赋值)。

foo() 由于对 undefined 值进行函数调用而导致非法操作,因此抛出 TypeError 异常。

同时也要记住,即使是具名的函数表达式(如上:bar),名称标识符在赋值之前也无法在所在作用域中使用。

JS 引擎理解为如下形式:

var foo;
foo(); // TypeError
bar(); // ReferenceError
foo = function() {
  var bar = ...self... // ...
}

函数优先

函数声明和变量声明都会被提升,函数会首先被提升,然后才是变量。

foo(); // 1
var foo;
function foo() {
  console.log(1);
}
foo = function() {
  console.log(2);
};

JS 引擎理解为如下形式:

function foo() {
  console.log(1);
}
foo(); // 1
foo = function() {
  console.log(2);
};

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

尽管重复的 var 声明会被忽略掉,但出现在后面的函数声明还是可以覆盖前面的,比如下面一段代码:

foo(); // 3
function foo() {
  console.log(1);
}
var foo = function() {
  console.log(2);
};
function foo() {
  console.log(3);
}

总结

声明本身 (变量声明和函数声明)会被提升,而包括函数表达式的赋值在内的 赋值操作 并不会提升。

要注意避免重复声明,特别是当普通的 var 声明函数声明混合在一起的时候,否则会引起很多危险的问题!

猜你喜欢

转载自blog.csdn.net/weixin_42134714/article/details/85692282