javascript ·

函数与变量的优先级

我们之前说过变量声明会出现变量提升的情况,这个问题说的已经很多了,但是我还是想在啰嗦一下。直觉上我们都会认为 JavaScript 是单线程语言,代码在执行时是由上到下一行一行执行的。但实际上这并不完全
正确,有一种特殊情况会导致这个假设是错误的,这种特殊情况不包括异步。

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

那么这个a应该输出什么呢,很多人认为会输出undefined,因为后面的声明变量将前面的覆盖了。事实上其结果却是输出2,就是因为变量提升的原因。我们再看一下下面这段代码

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

鉴于上一个代码片段所表现出来的某种非自上而下的行为特点,你可能会认为这个代码片段也会有同样的行为而输出 2。还有人可能会认为,由于变量 a 在使用前没有先进行声明,因此会抛出 ReferenceError 异常。

不幸的是两种猜测都是不对的。输出来的会是 undefined,原因也是因为变量提升的问题。

解释

JavaScript引擎会在解释 JavaScript 代码之前首先对其进行编译。编译阶段中的一部分工作就是找到所有的声明,并用合适的作用域将它们关联起来。因此,正确的思考思路是,包括变量和函数在内的所有声明都会在任何代码被执行前首先被处理。当你看到var a = 2; 时,可能会认为这是一个声明。但 JavaScript 实际上会将其看成两个声明:var a;a = 2;。第一个定义声明是在编译阶段进行的。第二个赋值声明会被留在原地等待执行阶段。

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

实际的执行效果如上代码所示。

函数声明

在写代码过程中,我们会发现无论我的函数声明写在哪都可以调用,原因就在于函数的声明也存在提升的现象。

oecom();
function oecom(){
    console.log("oecom")
}

这种方式去执行时不会报任何错误的,还会顺利的输出oecom。原因就在于函数的声明会在最开始声明,形成如下代码:

function oecom(){
    console.log("oecom")
}
oecom();

我们再来看另外一中形式

oecom();
var oecom = function(){
    console.log("oecom")
}

函数与变量的优先级
通过上图我们可以发现报错了。原因在于函数声明会被提升,但是函数表达式却不会被提升。同时也要记住,即使是具名的函数表达式,名称标识符在赋值之前也无法在所在作用域中。

oecom();
var oecom = function(){
    console.log("oecom")
}

函数与变量的优先级

函数优先

函数声明和变量声明都会被提升。但是一个值得注意的细节(这个细节可以出现在有多个“重复”声明的代码中)是函数会首先被提升,然后才是变量。

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 声明会被忽略掉,但出现在后面的函数声明还是可以覆盖前面的。

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

很多人认为这基本上没什么用,因为不会在一个作用域内声明相同名称的变量,但是来看一下下面这种代码是否写过呢

foo(); // "b"
var a = true;
if (a) {
 function foo() { console.log("a"); }
}
else {
 function foo() { console.log("b"); }
}

我们会发现这段代码执行完成以后会输出b,而且是永远输出b。因为一个普通块内部的函数声明通常会被提升到所在作用域的顶部,这个过程不会像上面的代码暗示的那样可以被条件判断所控制。

参与评论