JavaScript 系列 -- 变量提升和函数提升详解

340 阅读3分钟

前言

预编译后,变量 / 函数会被提升,变量 / 函数被提升过后,JS 引擎先对提升上来的所有对象统一执行一遍声明步骤,然后再对变量执行一次赋值步骤

变量提升

只有声明被提升到当前作用域的顶端,但初始化不会被提升

例子 1

console.log(num); // undefined
var num;
num = 6;

预编译

var num;
console.log(num); // undefined
num = 6; // 初始化不会被提升

例子 2

function hoistVariable() {
  if (!foo) {
    var foo = 5;
  }
  console.log(foo); // 5
}
hoistVariable();

预编译

function hoistVariable() {
  var foo     // 将 if 语句内的声明提升,但初始化没有被提升
  if (!foo) { // !undefined = true
    foo = 5;
  }
  console.log(foo); // 5
}
hoistVariable();

如果这里用 let 或 const 来声明变量,由于存在块级作用域概念,所以会报错

例子 3

var foo = 3;
function hoistVariable() {
  var foo = foo || 5;
  console.log(foo); // 5
}
hoistVariable();

预编译

var foo = 3;
function hoistVariable() {
  var foo
  foo = foo || 5; // 此时 等号右侧 foo 为 undefined
  console.log(foo); // 5
}
hoistVariable();

函数提升

  • 函数声明和初始化都会被提升
  • 函数表达式声明被提升,但初始化不会被提升

例子 1:函数声明和初始化都可被提升

console.log(square(5)); // 25
function square(n) {
  return n * n;
}

预编译

var square
square = function(n) {
  return n * n;
}
console.log(square(5)); // 25

例子 2:函数表达式不可被提升

console.log(square); // undefined
var square = function (n) { 
  return n * n; 
}

预编译

var square
console.log(square); // undefined =》赋值没有被提升
console.log(square(5)); // square is not a function =》 square 值为 undefined 故报错
square = function (n) { 
  return n * n; 
}

例子 3:函数和函数表达式混合

function hoistFunction() {
  foo(); // 2
  var foo = function() {
    console.log(1);
  };
  foo(); // 1
  function foo() {
    console.log(2);
  }
  foo(); // 1
}

hoistFunction();

预编译

function hoistFunction() {
  var foo
  foo = function() {
    console.log(2);
  }
  foo(); // 2
  foo = function() {
      console.log(1);
  };
  foo(); // 1
  foo(); // 1
}

hoistFunction();

优先级——变量和函数同时提升

  1. 函数声明被提升时,声明和赋值两个步骤都会被提升,而普通变量却只能提升声明步骤,而不能提升赋值步骤。
  2. 变量被提升过后,先对提升上来的所有对象统一执行一遍声明步骤,然后再对变量执行一次赋值步骤。而执行赋值步骤时,会优先执行函数变量的赋值步骤,再执行普通变量的赋值步骤

例子1

typeof a; // function
function a () {}
var a;
// typeof a; // function  =》无论放在前面还是后面,解析后执行顺序都是一样

预编译后

function a;  // => 声明一个function a
var a;       // =》 声明一个变量 a
a = () {};   // => function a 初始化
typeof a;    // function

例子2

function b(){};
var b = 11;
typeof b; // number

预编译后

function b;  // => 声明一个function b
var b;       // =》 声明一个变量 b
b = (){};    // =》 function b 初始化
b = 11;      // =》 变量 b 初始化 =》变量初始化没有被提升,还在原位
typeof b;    // number

例子3:结合自执行函数

var foo = 'hello';
(function(foo){
  console.log(foo);
  var foo = foo || 'world';
  console.log(foo);
})(foo); // 传入的 foo 的值是 'hello'
console.log(foo); // hello hello hello

预编译后

var foo = 'hello';
(function (foo) {
    var foo;  // undefined;
    foo= 'hello'; //传入的foo的值
    console.log(foo); // hello
    foo = foo || 'world';// 因为foo有值所以没有赋值world
    console.log(foo); //hello
})(foo);
console.log(foo);// hello,打印的是var foo = 'hello' 的值(变量作用域)