Featured image of post 【JavaScript 核心原理】hoisting 提升

【JavaScript 核心原理】hoisting 提升

【JavaScript 核心原理】hoisting 提升

Photo by Mohammad Rahmani on Unsplash

hoisting 提升逻辑

  • 变数宣告函式宣告 都会提升
  • 只有宣告会提升赋值不会提升
  • 函式宣告 提升高度高于 变数宣告
  • 函式里面传进来的参数

何谓 hoisting 提升呢?

在变数 还没被宣告 之前就使用,没有执行错误,表示此变数被 hoisting 提升

// undefined
console.log(a)
var a = 10

原本预期是 ReferenceError: a is not defined 出错,但是却显示 undefined

所以程式码被 JavaScript 核心编译处理后会变成这样,变数 ahoisting 提升

var a
// undefined
console.log(a)
a = 10

为什麽我们需要 hoisting?

如果我们没有 hoisting 会怎样?

1. 先宣告变数才可以使用

变数使用前先宣告是个好习惯

2. 先宣告函式才可以使用

每个档案你都必须把 function 宣告放到最上面去,才能保证你底下的程式码都可以 call 到这些 function

3. 没有办法达成 function 互相呼叫

尽量不要做到 function 彼此互相 call,这样很容易一写不好导致无穷迴圈

function loop(n){
    if (n>1) {
        // 呼叫纪录奇数偶数函数
        logEvenOrOdd(--n)
    }
}

function logEvenOrOdd(n) {
    console.log(n, n % 2 ? 'Odd' : 'Even');
    // 呼叫  loop 函数
    loop(n);
}

// 9 Odd
// 8 Even
// 7 Odd
// 6 Even
// 5 Odd
// 4 Even
// 3 Odd
// 2 Even
// 1 Odd
loop(10)

所以为了能够达成上面的结果,所以才需要 hoisting 提升

hoisting 提升范例

变数在 function 参数

function test(v){
    //  10
    console.log(v);
    var v = 3
}
test(10)

变数 v 印出来的是 10 而不是 undefined

因为变数 v 从外部传入数值,所以表示此变数已经预先定义,所以 hoisting 提升 会变成

function test(v){
  // 因为下面呼叫 test(10)
  var v = 10
  var v
  console.log(v)
  v = 3
}
test(10)

let 宣告变数 hoisting 提升

let 变数宣告 不会进行 hoisting 提升

// ReferenceError: a is not defined
console.log(a)
let a

let 变数宣告 导致函数内部原本 global 变数 a 变成 local 变数 a,而 let 变数宣告 不会进行 hoisting 提升到使用出错

var a = 10
function test(){
  // ReferenceError: Cannot access 'a' before initialization
  console.log(a)
  let a
}
test()

function 变数提升

function 也会被 hoisting 提升

greeting('KJ');
function greeting(name){
    // Hello!  KJ
    console.log('Hello! ',name)
}

使用 var 定义变数实则会无法提升

//  TypeError: greeting is not a function
greeting('KJ');
var greeting = function(name){
    console.log('Hello! ',name)
}
// ReferenceError: greeting is not defined
greeting('KJ');
greeting = (name) => {
    console.log('Hello! ',name)
}

实际上提升的方式结果会变成 undefined 的变数值,所以在执行的时候会出错

var greeting;
// 这时 greeting 是 undefined
greeting('KJ');
greeting = function(name){
  console.log('Hello! ',name)
}

function 与变数相同同步提升

var a = 0;
function a() {};
// TypeError: a is not a function
a();

function 会优先提升,再来是变数,所以提升完后会长这样

function a() {};
var a;
a = 0
a();

这样导致变数后来被赋值,变成不是 function 而导致无法呼叫

JavaScript 引擎操作

var foo = "bar"
var a = 1
function bar() {
    foo = "inside bar"
    var a = 2
    c = 3
    console.log(c)
    console.log(d)
}
bar()

执行完变数的 scope 及设定会长这样,会将需要的变数及函式进行 hoisting 提升

globalScope = {
  foo: undefined,
  a: undefined,
  bar: function(){}
}

barScope: {
  a: undefined
}

LHS(Left hand side) 与 RHS(Right hand side)

名词 说明 范例
LHS 请帮我去查这个变数的 位置 在哪里,因为 我要对它赋值 let a = 1
RHS 请帮我查询这个变数的 值是什麽,因为 我要用 这个值 console.log(a)

变数定义步骤

var foo = "bar"
var a = 1
function bar() {
    foo = "inside bar"
    var a = 2
    c = 3
    console.log(c)
    console.log(d)
}
bar()

Step 1. var foo = “bar”

步骤 程式 处理 结果
1 var foo = "bar" 问 global scope,我要对 foo 变数 LHS 你有看过吗? global scope 成功找到 foo 并且赋值
globalScope : {
  foo: "bar",
  a: undefined,
  bar: function(){}
}

barScope: {
  a: undefined
}

Step 2. var a = 1

步骤 程式 处理 结果
2 var a = 1 问 global scope,我要对 a 变数 LHS 你有看过吗? global scope 成功找到 a 并且赋值
globalScope : {
  foo: "bar",
  a: 1,
  bar: function(){}
}

barScope: {
  a: undefined
}

Step 3. bar()

步骤 程式 处理 结果
3 bar() 问 global scope,我要对 bar 变数 LHS 你有看过吗? global scope 成功找到 bar 并且执行 function
globalScope : {
  foo: "bar",
  a: 1,
  bar: function(){}
}

barScope: {
  a: undefined
}

Step 4. foo = “inside bar”

步骤 程式 处理 结果
4 foo = "inside bar" 依序问 bar scope 及 global scope,我要对 bar 变数 LHS 你有看过吗? bar scope 没有找到,但 global 成功找到 foo 并且赋值
globalScope : {
  foo: "inside bar",
  a: 1,
  bar: function(){}
}

barScope: {
  a: undefined
}

Step 5. var a = 2

步骤 程式 处理 结果
5 var a = 2" 问 bar scope,我要对 a 变数 LHS 你有看过吗? bar scope 成功找到 a 并且赋值
globalScope : {
  foo: "inside bar",
  a: 1,
  bar: function(){}
}

barScope: {
  a: 2
}

Step 6. c = 3

步骤 程式 处理 结果
6 c = 3" 依序问 bar scope 及 global scope,我要对 c 变数 LHS 你有看过吗? bar scope 及 global scope 都没有找到

严格模式(use strict) 下会回传 ReferenceError: c is not defined

不是在严格模式,则会宣告变数 c 到 global scope

globalScope : {
  foo: "inside bar",
  a: 1,
  bar: function(){},
  // 非严格模式
  c: 3
}

barScope: {
  a: 2
}

Step 7. console.log(c)

步骤 程式 处理 结果
7 console.log(c)" 依序问 bar scope 及 global scope,我要对 c 变数 RHS 你有看过吗? bar scope 没有找到,但 global 成功找到 c 并且呼叫 console.log
globalScope : {
  foo: "inside bar",
  a: 1,
  bar: function(){},
  // 非严格模式
  c: 3
}

barScope: {
  a: 2
}

Step 8. console.log(d)

步骤 程式 处理 结果
8 console.log(d)" 依序问 bar scope 及 global scope,我要对 d 变数 RHS 你有看过吗? bar scope 及 global scope 都没有找到,回传 ReferenceError: d is not defined
globalScope : {
  foo: "inside bar",
  a: 1,
  bar: function(){},
  // 非严格模式
  c: 3
}

barScope: {
  a: 2
}

Temporal Dead Zone (TDZ)

var & let & const 都有 hoisting

  • var 提升后变数会初始化为 undefined
  • let 与 const 不会初始化,所以在取用前必须要赋值,不然就会出错

所以 let 与 const 未被赋值前,会进入一个 Temporal Dead Zone (TDZ) 无法存取

function test() {
    var a = 1; // c 的 TDZ 开始
    var b = 2;
    // ReferenceError: Cannot access 'c' before initialization
    console.log(c) // 错误
    if (a > 1) {
        console.log(a)
    }
    let c = 10 // c 的 TDZ 结束
}
test()

参考资料

Donate KJ 贊助作者喝咖啡

如果這篇文章對你有幫助的話,可以透過下面支付方式贊助作者喝咖啡,如果有什麼建議或想說的話可以贊助並留言給我
If this article has been helpful to you, you can support the author by treating them to a coffee through the payment options below. If you have any suggestions or comments, feel free to sponsor and leave a message for me!
方式 Method 贊助 Donate
PayPal https://paypal.me/kejyun
綠界 ECPay https://p.ecpay.com.tw/AC218F1
歐付寶 OPay https://payment.opay.tw/Broadcaster/Donate/BD2BD896029F2155041C8C8FAED3A6F8
All rights reserved,未經允許不得隨意轉載
Built with Hugo
主题 StackJimmy 设计