你不知道的JS之作用域和闭包(四)(声明)提升

  原文:你不知道的js系列

先有鸡还是先有蛋?

 

如下代码:

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

很多开发者可能会认为结果会输出 undefined,因为 var a 在 a = 2 后面,好像变量似乎被重新定义了,所以结果会是默认值 undefined。

然而,正确结果是 2.

 

下面这段代码,a 在 定义之前被引用。不会抛出错误,也不会输出 2 。

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

结果输出 undefined。

 

编译器

回忆一下第(一)节中的内容,引擎在解释执行之前会编译你的代码。

所有的声明,包括函数和变量,都会在代码执行之前被处理。

你看到 var a = 2;是一个语句,实际上 JavaScript 会认为这是两个语句,第一个声明 var a 在编译期间被处理,第二个语句赋值操作,留在原处等待执行。

所以上面的第一段代码会被处理成两部分

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

 

第二段代码实际上也被处理成:

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

 

这个过程,可以看成是变量和函数的声明被提升到代码的头部,叫做变量提升(Hoisting)

也就是说变量声明在赋值语句之前被执行。

注:只有声明被提升,赋值和其它的操作被留在原处。

 

foo();

function foo() {
    console.log( a ); // undefined

    var a = 2;
}

上面代码中的函数 foo 的声明被提前,所以这样的调用是可以被执行的。

 

提升发生在每个作用域内部,所有变量 a 的声明被提升到 foo 的顶部。相当于:

function foo() {
    var a;

    console.log( a ); // undefined

    a = 2;
}

foo();

 

函数声明被提升了,但是函数表达式不会被提升:

foo(); // not ReferenceError, but TypeError!

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

这里的变量 foo 的声明被提升了,所以对 foo 的 RHS 查询不会报错。但这个时候 foo 的值还是 undefined,所有会报 TypeError 错误。

就算是有命名的函数表达式,这个名称标识也不会被外部作用域访问:

foo(); // TypeError
bar(); // ReferenceError

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

上面这段代码会被解释为类似下面的:

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 );
};

输出结果为1

因为这段代码会被解释为:

function foo() {
    console.log( 1 );
}

foo(); // 1

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

var foo 是一个重复的声明,所有被忽略掉了。即时它在函数声明的前面编写,因为函数声明的提升要先于变量声明的提升。

和函数名称重复的变量声明就会被忽略,但如果有多个同名的函数声明,那么后面的声明会覆盖掉前面的:

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" ); }
}

 

但这种行为并不靠谱,因为有可能将来 JavaScript 的版本会改变这个机制(比如在块中声明的函数将会绑定在块作用域中),所以要避免在块中声明函数。

 

小结

变量声明提升,函数声明提升,其它操作不变

函数表达式不会被提升

函数声明优先于变量声明

后声明的会覆盖先声明的

posted @ 2019-01-24 17:38  隙游尘  阅读(169)  评论(0编辑  收藏  举报

Hi