Photo by Mohammad Rahmani on Unsplash
hoisting 提升逻辑
变数宣告
跟函式宣告
都会提升- 只有
宣告会提升
,赋值不会提升 函式宣告
提升高度高于变数宣告
- 函式里面传进来的参数
何谓 hoisting 提升呢?
在变数 还没被宣告
之前就使用,没有执行错误,表示此变数被 hoisting 提升
了
// undefined
console.log(a)
var a = 10
原本预期是 ReferenceError: a is not defined
出错,但是却显示 undefined
所以程式码被 JavaScript 核心编译处理后会变成这样,变数 a
被 hoisting 提升
了
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()
参考资料
- 我知道你懂 hoisting,可是你了解到多深?
- You-Dont-Know-JS/scope-closures at 2nd-ed · getify/You-Dont-Know-JS · GitHub
- You-Dont-Know-JS/ch5.md at 2nd-ed · getify/You-Dont-Know-JS · GitHub
- 一次说清楚 JavaScript 中宣告的各种提升行为(var、function、let/const) | by realdennis | Medium
- [第十七週] JavaScript 进阶:从 EC 来理解 Hoisting | Yakim shu
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 |