javaScript 中的变量提升
系列开篇
为进入前端的你建立清晰、准确、必要的概念和这些概念的之间清晰、准确、必要的关联, 让你不管在什么面试中都能淡定从容。没有目录,而是通过概念关联形成了一张知识网络,往下看你就明白了。当你遇到【关联概念】时,可先从括号中的(强/弱)判断简单这个关联是对你正在理解的概念是强相关(得先理解你才能继续往下)还是弱相关(知识拓展)从而提高你的阅读效率。我也会定期更新相关关联概念。
面试题
- 变量提升是什么
- 各种看程序写打印结果的笔试题
这是干什么的?
我们习惯将var a = 2;
这条语句看作一个声明,而实际上JavaScript引擎并不这么认为。
它将var a
和 a = 2
当作两个单独的声明,
第一个是编译阶段的任务,
而第二个则是执行阶段的任务。
所以第一个 var a
声明都会在第二阶段之前首先被处理。
简单来说,包括变量和函数在内的所有声明都会在任何代码被执行前首先被处理。
可以将这个过程形象地想象成: 变量和函数声明从它们在代码中出现的位置被“移动”到了最上面(各自作用域的最顶端)。这个过程就叫作提升。
但是要注意 只有声明本身会被提升,而(包括函数表达式的赋值)赋值操作或其他运行逻辑会留在原地并不会被提升。
函数声明也会被提升,而且会被整体提升。并且函数提升优先于变量提升,称为函数优先。但是函数表达式却不会被提升。
例子
变量声明提升 和 函数声明整体提升
foo(); // foo 函数的声明被提升了,因此第一行中的调用可以正常执行。
function foo() {
console.log( a ); // undefined
var a = 2;
}
// 编译器过程可看做下面的形式
function foo() {
var a;
console.log( a ); // undefined
a = 2;
}
foo();
函数表达式(即使是具名函数)不会被提升
foo(); // 不是 ReferenceError, 而是 TypeError foo is not a function
var foo = function bar() {
// ...
};
这段程序中的变量标识符 foo() 被提升并分配给所在作用域(在这里是全局作用域),因此foo() 不会导致 ReferenceError。但是 foo 此时并没有赋值(如果它是一个函数声明而不是函数表达式,那么就会赋值)。foo() 由于对undefined 值进行函数调用而导致非法操作,因此抛出 TypeError 异常。
函数优先
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()...
的声明之前,但它是重复的声明(因此被忽略了),因为函数声明会被提升到普通变量之前。
原理是什么?
我们深刻理解了这个概念之后,可以探究下它的实现(面试也经常问到这方面源码),可能有人觉得没啥用,我觉得它的用处是拓展出其他相关联的【必知】概念,也可以看看你的硬编码能力,再不济看看你的记忆力如何也是好的。(^-^)
在词法环境或变量环境执行上下文 -> 词法环境【关联子概念(强)】的创建阶段,函数声明存储在环境中,而变量会被设置为 undefined(在 var 的情况下)或保持未初始化 Uninitialized(在 let 和 const 的情况下)。
所以这就是为什么可以在声明之前访问 var 定义的变量(尽管是 undefined ),但如果在声明之前访问 let 和 const 定义的变量就会提示引用错误的原因。
参考
- You don't know Javascript