# 变量提升和函数提升

# 变量提升

js在执行过程中进入新的函数时,函数内部被声明的所有变量都会被移动到函数最开始的地方。被提升的只有变量的声明,赋值还在原来的位置。变量会优先查找当前函数内部被声明的局部变量,找不到才会找全局变量。

变量提升只有声明被提示,而不提示初始化

变量提升是将变量声明提升到它所在当前作用域{}的最开始的部分。

console.log(a)
var a = 1;

// 变量提升后等价于下面代码

var a;
console.log(a) // undefined
a = 1

首先在当前作用域内寻找,当前作用域没有变量声明再去外部寻找。

var a = 1;
function fn() {
  console.log(a)
  var a = 2;
  console.log(a)
}

// 变量提升后等价于下面代码

var a = 1;
function fn() {
  var a;  // 就是外部有同名的变量,也要先查找当前作用域下的声明的变量
  console.log(a) // undefined
  a = 2;
  console.log(a) // 2
}

# 函数提升

js中创建函数有两种方式:函数声明式和函数字面量式。只有函数声明才存在函数提升 表达式通过变量保存函数和变量提升一样。

console.log(f1);  
console.log(f2);  
function f1() {}
var f2 = function() {}

// 函数提升后等价于下面代码

function f1() {}
var f2;
console.log(f1); // function f1() {}   
console.log(f2); // undefined  这种声明就相当于变量提升一样了
f2 = function() {}

# 变量提升与函数提升

函数提升优先于变量提升

看一道面试题,执行fn输出什么

var a = 1;
function fn(a) {
  console.log(a)
  var a = 2;
  function a() {}
  console.log(a)
}

// 分析

var a = 1;
function fn(a) {
  console.log(a) // function a() {}
  function a() {} // 变量提升到最前面
  var a;          // 行参相当于var a,所以也在函数后面
  a = 3;
  var a;          // 函数内部声明的a,覆盖了形参
  a = 2;
  console.log(a) // 2
}

// 所以输入如下
fn(3)
// ƒ a() {}
// 2

另一道面试题:

var a=2;
function a() {
  console.log(3);
}
console.log(typeof a);

// 变量提升和函数提升后,代码变为

function a() {
  console.log(3);
}
a=2;
console.log(typeof a); // number

变量和函数提升后,再按顺序执行,在上面先获取上面的,在下面就获取最下面的

console.log(a);
var a=2;
function a() {}

// 相当于

console.log(a); // function a() { }
function a() {}
var a=2;
var a=2;
function a() {}
console.log(a);

// 相当于

function a() {}
var a=2;
console.log(a); // 2

# var function 和 let const

js中无论哪种形式声明(var,let,const,function,function*,class)都会存在提升现象,不同的是, var,function,function*的声明会在提升时进行初始化赋值为 undefined,因此访问这些变量的时候,不会报ReferenceError异常,而使用 let,const,class 声明的变量,被提升后不会被初始化,这些变量所处的状态被称为“temporal dead zone”,此时如果访问这些变量会抛出ReferenceError异常,看上去就像没被提升一样。

# 为什么有变量提升

js和其他语言一样,都要经历编译和执行阶段。而js在编译阶段的时候,会搜集所有的变量声明并且提前声明变量,而不会改变其他语句的顺序,因此,在编译阶段的时候,第一步就已经执行了,而第二步则是在执行阶段执行到该语句的时候才执行。

补充:

变量提升的本质其实是由于js引擎在编译的时候,就将所有的变量声明了,因此在执行的时候,所有的变量都已经完成声明。

当有多个同名变量声明的时候,函数声明会覆盖其他的声明。如果有多个函数声明,则是由最后的一个函数声明覆盖之前所有的声明。

遇到面试考察变量提升问题,首先就要先提升变量,再考虑其他的。