前言
相信经历了前面的学习,我们已经熟练的掌握了作用域,以及变量分配的原理。总的来说,就是声明在某区域的变量,都附属于这个作用域。
但作用域与当中的变量声明出现的位置有某种联系,这篇文章将会讨论这个细节问题。
运行顺序
以我们的写作习惯来说。我们一般认为,代码的执行就是一行一行依次往下运行过去的啊。事实却并不完全如此,还是存在特殊情况,导致我们的想法错误。
以下面的代码做一个例子:
a = 2;
var a;
console.log( a );
让我们思考一下,这里的console.log()会输出什么呢?
许多开发者会认为是undefined
,因为var a
声明在 a=2
后面。按照代码顺序,我们自然会认为a被重新赋值了,所以会被赋予默认值undefined
。但是,真正的输出结果是2
。
让我们再看一段代码:
console.log(a);
var a = 2;
这又会输出什么呢?
有了上面的教训,我们可能会认为这也会输出2
。有些人又有其他意见了:变量a在使用之前没有声明啊!因此会抛出ReferenceError
错误才对。
糟糕的是两种意见都是错误的,这里会输出undefined
。此刻我们好像陷入了一个经典哲学性问题中:到底是先有鸡(赋值),还是先有蛋(声明)?
编译器又出现
为了弄清这个问题,我们要先复习一下编译器的内容。[详情请看](大厂面试官(2):请你细说作用域的底层运算逻辑。 - 掘金)。引擎会在解释JavaScript代码之前进行编译。编译中的一部分工作就是找到所有的声明,并用合适作用域将其串联起来。
所以,包括函数和变量在内的所有声明都会在代码执行前首先被处理。
当我们看到var a = 2
,第一反应会认为这是一个声明。但JavaScript会把他分为两个部分var a
和a =2
,第一个在编译阶段执行,第二个在执行阶段执行。
上面的第一个代码会被认为是:
var a ;
a = 2 ;
console.log(a);
其中第一部分是编译,第二部分是执行
类似,第二个代码实际上是以这样的流程执行的
var a ;
console.log(a);
a =2 ;
因此,变量和函数声明从代码中的位置被移动到最上方,这个过程就是提升
。
所以这个哲学问题也能解决:先有蛋(声明),再有鸡(赋值)。
注意
:只有声明会被提升,而赋值和其他代码会留在原地。如果提升改变了代码执行顺序,会受到严重的破坏。
函数优先
函数声明和变量声明都会进行提升。但是函数会被首先提升,然后再是变量。
思考以下代码:
foo();// 1
var foo;
function foo() {
console.log( 1 );
}
foo = function() {
console.log( 2 );
}
此时代码会输出1
而不是2
。
引擎眼中的代码是:
function foo() {
console.log( 1 );
}
foo(); // 1
foo = function() {
console.log( 2 );
}
此时尽管var foo
出现在function foo
之前,但他是重复的声明(被忽略),因此函数声明会被提升到普通变量之前。
小结
我们习惯将var a = 2;看作一个声明,而实际上JavaScript引擎并不这么认为。它将var a 和a = 2当作两个单独的声明,第一个是编译阶段的任务,而第二个则是执行阶段的任务。
这意味着无论作用域中的声明出现在什么地方,都将在代码本身被执行前首先进行处理。 可以将这个过程形象地想象成所有的声明(变量和函数)都会被“移动”到各自作用域的 最顶端,这个过程被称为提升。
声明本身会被提升,而包括函数表达式的赋值在内的赋值操作并不会提升。
要注意避免重复声明,特别是当普通的var声明和函数声明混合在一起的时候,否则会引 起很多危险的问题