blog
blog copied to clipboard
揭开Hoisting面纱
揭开Hoisting面纱
简单来说,就是在JS的作用域里,变量不一定要先定义后使用,JS对变量的处理规则和我们在其它编程语言中的经验有些差别。
JS是一门编译型语言
对于var a= 2
这样的语句来说,JS引擎是分成两部分处理的:
- 第一部分是声明变量:
var a;
,这部分可以理解成是在编译期完成的,它确定了变量的作用域; - 第二部分是赋值:
a = 2;
,编译器生成表示赋值的指令,由JS引擎在运行时完成;
在一个作用域里,被编译器提前的,都是变量的声明,而之后要执行的代码(对我们的例子来说,也就是赋值),则会保留在原地。这就是hoisting的含义。 这也很好理解,如果hoisting可以改变代码的执行顺序,我们就很难写出工作正常的程序了。
foo();
function foo() {
console.log(a);
var a = 2;
}
执行这段代码,同样会得到undefined结果。这里,有两个原因:
- 第一、函数声明同样得到了提升,表明foo是一个函数,因此,在定义foo之前,我们可以直接调用它;
- 第二、Hoisting是按作用域发生的,声明只会提升到每个作用域的顶端;
于是,上面这段代码会被编译器处理成这样:
// Hoisting in global scope
function foo() {
// Hoisting in function scope
var a;
console.log(a);
a = 2;
}
foo();
可以看到,hoisting一共发生了两次,分别在全局以及函数作用域内。因此,执行foo的时候,会看到undefined。
最后,我们再来看一个无法提升的例子:
bar();
var bar = (function foo() {
console.log(a);
var a = 2;
});
所以就函数来说,函数声明是可以提升的,但是函数表达式不行。
变量提升的优先级
既然,函数和变量声明都有可能提升,当存在同名的变量和函数时:
- 函数声明优先提升,多个同名函数的声明后者覆盖前者
foo(); // 1
var foo;
function foo() {
console.log(1);
}
foo = function() {
console.log(2);
}
第一个调用的结果是什么呢?当JS引擎编译的时候,变量foo和函数foo都可以提升,但是函数foo会优先得到提升,于是我们就可以在控制台看到结果1了。