(深究)声明提前(Hoisting)

简介

JavaScript的函数作用域是指在函数内声明的所有变量在函数体内始终是可见的。有意思
的是,这意味着变量在声明之前甚至已经可用。JavaScript的这个特性被非正式地称为
声明提前(hoisting) ,即JavaScript函数里声明的所有变量(但不涉及赋值)都被“提
前”至函数体的顶部。如果对提升不太明白的,请参考JavaScript高级程序设计177页函数表达式.MDN变量提升

示例

函数声明:

sayHi(); //不会报错,在执行代码之前会先读取函数声明,也就是 函数声明提升

function sayHi(){

  alert('Hi')

}

通过提升其实是这样的:

//函数声明提升到顶部

function sayHi(){

  alert('Hi')

}

sayHi(); //不会报错
函数表达式:

sayHi(); // 报错  

var sayHi = function(){

  alert('Hi')

}
函数表达式其实是创建一个匿名函数然后赋值给变量,通过提升应该是这样的:

//函数声明和变量声明都被提升到作用域顶部,函数优先

functionsayHi(){

  alert('Hi')

}

//变量声明被提升到顶部

var sayHi;

sayHi(); // 报错  

//变量赋值被留在原地

sayHi = function(){

  alert('Hi')

}
函数提升优先级高于变量提升
a()  // alert(1)
var a = 1
function a(){
  alert(1)
}

通过提升:
function a(){
  alert(1)
}
var a 
a()
a = 1

提问

为什么会有提升

比起那些编译过程只有三个步骤的语言的编译器,JavaScript引擎要复杂得多。例如,在语法分析 和代码生成阶段有特定的步骤来对运行性能进行优化,包括对冗余元素进行优化等。我们通过编译器在词法阶段进行词法分析,生成词法作用域,词法作用域就是用来管理引擎如何在当前作用域以及嵌套 的子作用域中根据标识符名称进行变量查找。提升的作用使得所有声明都在词法作用域的上方,这样引擎在作用域及嵌套作用域中变量查找可以更快更简单。

提升的机制

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

let究竟有没有提升

MDN_let及暂存死区

var i = 10;
function a(){
   i = 3;
   var i = 1;  //因为 var 提升了 所以在 function里 创建了 局部变量 i
}
console.log(i) // 10
let a = 1
{
  a = 2;
  let a  = 3;   //报错
}

//这里的报错,我认为是规范化强制性报错,并不代表let没有提升
//我的猜想如下,

let a = 1
{
  let a // 暂存死区 开始的地方就是这里
  a = 2 // 由于 a = 2 在 暂存死区 中,所以报错
  a // 暂存死区 结束的地方就是这里
}

为什么let要强制性报错

不推荐使用eval及with等,使用let/const 代替var,使用块级作用域
目的就是为了,减少词法欺骗以及作用域的混乱导致的性能问题,使得javascript引擎运行更快。
使用let 就要按照let 的使用规定,不然和使用var又没什么区别了,这是我的想法。

为什么函数比变量提升优先级更高

未完待续...
欢迎补充批评...

参考文章:
知乎提问
strack overflow
JavaScript引擎解析预编译
我的博客园
《你不知道的JavaScript 上》

相关推荐