[关联概念] 一文说透 JS 中的变量提升

779 阅读3分钟

javaScript 中的变量提升

系列开篇

为进入前端的你建立清晰、准确、必要概念和这些概念的之间清晰、准确、必要关联, 让你不管在什么面试中都能淡定从容。没有目录,而是通过概念关联形成了一张知识网络,往下看你就明白了。当你遇到【关联概念】时,可先从括号中的(强/弱)判断简单这个关联是对你正在理解的概念是强相关(得先理解你才能继续往下)还是弱相关(知识拓展)从而提高你的阅读效率。我也会定期更新相关关联概念。

面试题

  • 变量提升是什么
  • 各种看程序写打印结果的笔试题

这是干什么的?

我们习惯将var a = 2;这条语句看作一个声明,而实际上JavaScript引擎并不这么认为。

它将var aa = 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