变量声明
先来看两段代码,正确的运行结果已经给出,
a = 2;var a;console.log(a); // 2复制代码
console.log(a); // undefinedvar a = 2;复制代码
直觉上会认为 JavaScript 代码在 执行时 是由上到下一行一行执行的,但实际上这并不完全正确。
JS 引擎会在解释 JavaScript代码之前首先对其进行编译。 编译阶段中的一部分工作就是找到所有的声明,并用合适的作用域将它们关联起来。
第一个代码片段会以如下形式进行处理:
var a;a = 2;console.log(a);复制代码
第二个代码片段实际是按照以下流程处理的:
var a;console.log(a);a = 2;复制代码
正确的思考思路是,
包括变量和函数在内的所有声明都会在任何代码被执行前首先被处理。 这意味着无论作用域中的声明出现在什么地方,都将在代码本身被执行前首先进行处理,每个作用域都会进行提升操作。当你看到 var a = 2
; 时,可能会认为这是一个声明,
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(); // TypeErrorbar(); // ReferenceErrorvar foo = function bar() { // ...};复制代码
这段代码的变量标识符 foo
被提升并分配给所在作用域(在这里是全局作用域),
foo()
不会导致 ReferenceError 。 但是 foo
此时并没有赋值(如果它是一个函数声明而不是函数表达式,那么就会赋值)。foo()
由于对 undefined
值进行函数调用而导致非法操作,因此抛出 TypeError 异常。 同时也要记住,即使是具名的函数表达式(如上:bar
),名称标识符在赋值之前也无法在所在作用域中使用。
JS 引擎理解为如下形式:
var foo;foo(); // TypeErrorbar(); // ReferenceErrorfoo = function() { var bar = ...self... // ...}复制代码
函数优先
函数声明和变量声明都会被提升,函数会首先被提升,然后才是变量。
foo(); // 1var foo;function foo() { console.log(1);}foo = function() { console.log(2);};复制代码
JS 引擎理解为如下形式:
function foo() { console.log(1);}foo(); // 1foo = function() { console.log(2);};复制代码
var foo
尽管出现在 function foo()...
声明之前,但它是重复的声明(因此被忽略了),因为函数声明会被提升到普通变量之前。
尽管重复的 var 声明会被忽略掉,但出现在后面的函数声明还是可以覆盖前面的,比如下面一段代码:
foo(); // 3function foo() { console.log(1);}var foo = function() { console.log(2);};function foo() { console.log(3);}复制代码
总结
声明本身(变量声明和函数声明)会被提升,而包括函数表达式的赋值在内的赋值操作并不会提升。
要注意避免重复声明,特别是当普通的var声明和函数声明混合在一起的时候,否则会引起很多危险的问题!
原文参考:《你不知道的 JavaScript》(上卷)第一部分 第四章。