Promise
是用來解決 AJAX 非同步問題,優化非同步程式的 ES6 語法,會回傳「接受」或「拒絕」的結果。
AJAX 是 JavaScript 與 XML 技術的縮寫,網頁不用重新整理,就能即時透過瀏覽器跟伺服器溝通、撈取資料。
function promise(num) {
return new Promise((resolve, reject) => {
num ? resolve(num) : reject('失敗')
})
}
以下整理一些 Promise 方法:
Promise.resolve
和 Promise.reject
都是直接定義 Promise
物件已完成的狀態,會產生一個新的 Promise
物件。
const result = Promise.resolve('result')
result.then(res => {
console.log(res, 'success') // 成功部分可以正確接收結果
}).catch(res => {
console.log(res, 'fail') // 失敗部分不會取得結果
})
---
const result = Promise.reject('result')
result.then(res => {
console.log(res, 'success')
}).catch(res => {
console.log(res, 'fail') // 只會取得失敗結果
})
Promise.race
會透過陣列執行多個 Promise
,但僅會回傳第一個結果,無論結果為成功或失敗。
Promise.all
會透過陣列傳入多個 Promise
函式,在「全部執行完後」回傳陣列局果,並且順序與一開始傳入一致,適合「多支API一起執行」,確保全部完成後才進行其他工作。
Promise.all([promise(1), promise(2), promise(3)])
.then((res) => {
console.log(res) // ["1", "2", "3"]
})
Fetch
會使用 ES6 的 Promise 作回應(then、catch),回傳的資料為 ReadableStream
物件,需要使用不同資料類型的應對方法,才能正確取得資料。
ReadableStream
的資料類型有:text()
、json()
、formData()
、blob()
、arrayBuffer()
const url = 'https://raw.githubusercontent.com/hexschool/hexschoolNewbieJS/master/data.json'
fetch(url)
.then(res => {
return res.json() // 先轉成 json 格式
})
.then(res => {
console.log(res) // 再接收轉換後的資料
})
.catch(res => {
console.log(res)
})
參考文章:
]]>Promise
是用來解決 AJAX 非同步問題,優化非同步程式的 ES6 語法,會回傳「接受」或「拒絕」的結果。
AJAX 是 JavaScript 與 XML 技術的縮寫,網頁不用重新整理,就能即時透過瀏覽器跟伺服器溝通、撈取資料。
function promise(num) {
return new Promise((resolve, reject) => {
num ? resolve(num) : reject('失敗')
})
}
以下整理一些 Promise 方法:
Promise.resolve
和 Promise.reject
都是直接定義 Promise
物件已完成的狀態,會產生一個新的 Promise
物件。
const result = Promise.resolve('result')
result.then(res => {
console.log(res, 'success') // 成功部分可以正確接收結果
}).catch(res => {
console.log(res, 'fail') // 失敗部分不會取得結果
})
---
const result = Promise.reject('result')
result.then(res => {
console.log(res, 'success')
}).catch(res => {
console.log(res, 'fail') // 只會取得失敗結果
})
Promise.race
會透過陣列執行多個 Promise
,但僅會回傳第一個結果,無論結果為成功或失敗。
Promise.all
會透過陣列傳入多個 Promise
函式,在「全部執行完後」回傳陣列局果,並且順序與一開始傳入一致,適合「多支API一起執行」,確保全部完成後才進行其他工作。
Promise.all([promise(1), promise(2), promise(3)])
.then((res) => {
console.log(res) // ["1", "2", "3"]
})
Fetch
會使用 ES6 的 Promise 作回應(then、catch),回傳的資料為 ReadableStream
物件,需要使用不同資料類型的應對方法,才能正確取得資料。
ReadableStream
的資料類型有:text()
、json()
、formData()
、blob()
、arrayBuffer()
const url = 'https://raw.githubusercontent.com/hexschool/hexschoolNewbieJS/master/data.json'
fetch(url)
.then(res => {
return res.json() // 先轉成 json 格式
})
.then(res => {
console.log(res) // 再接收轉換後的資料
})
.catch(res => {
console.log(res)
})
參考文章:
]]>在學習同步與非同步的概念時很容易混淆,「同步」光看字面上的意思,可能會誤解成「所有動作同時進行」,而其實正好相反。
就以去夜市點餐來說明,如果今天想買的東西有:雞排、QQ球、珍奶。
同步 (sync):先去買雞排 -> 再去買QQ球 -> 然後再買珍奶。
像這樣「先完成 A 才能做 B、C、D ...」的運作方式我們就會把它稱作「同步」。
非同步 (async):再找兩個朋友幫忙,每人去一個攤位買一樣東西,就可以同時買好雞排、QQ球、珍奶。
所以同步的概念其實比較像是「一步一步來處理」的意思。
非同步則是,我不用等待 A 做完才做 B、C,而是這三個事情可以同時發送出去。
Promise 是 ES6 新增的建構函式,用來優化非同步的語法,增加可讀性。
Promise 字面上翻譯就是「約定、承諾」,可以想像成 A 和 B 約定要做某件事情,接著回報處理結果,而這個結果只能是「完成」或「拒絕」。
function asyncFunction (value) {
return new Promise((resolve, rejecte) => {
value ? resolve('處理完成的結果') : reject('被拒絕的原因')
})
}
JavaScript 是單執行緒(同步)的程式語言,一次只能處理一件事情,所以遇到非同步的事件時,就會先把程式碼放到「事件佇列」,等到所有事件處理完後才會執行非同步事件。
console.log('開始') // 執行順序 1
setTimeout(() => {
console.log('非同步事件') // 執行順序 3
}, 0)
console.log('程式碼結束') // 執行順序 2
Promise 在處理非同步的事件的過程中,包含不同的進度狀態
resolve
的結果reject
的結果Promise 可以用 then
和 catch
來接收並回傳結果:
then
可以同時接收成功、失敗結果,而 catch
只接收失敗結果
reject
時,都會直接跳到 catch
,之後的 then
都不會執行catch
依然可以使用 return
繼續串接,但很少這樣使用promise(1)
.then(success => {
console.log(success) // resolve 接收成功 // '1'
return promise(0) // return promise(0) -> reject 跳到 catch
})
.then(success => { // 因為上面出現 reject 所以跳過
console.log(success)
return promise(3)
})
.catch( fail => {
console.log(fail) // reject 接收失敗 // '失敗'
})
then
中的兩個函式必定執行其中一個,可以用此方式確保所有的鏈接都能夠被執行promise(0)
.then(success => {
console.log(success)
return promise(1)
}, fail => {
console.log(fail) // reject 接收失敗 // '失敗'
return promise(2) // return promise(2)
})
.then(success => {
console.log(success) // resolve 接收成功 // '2'
return promise(0) // return promise(0)
}, fail => {
console.log(fail)
return promise(4)
})
.then(success => {
console.log(success)
}, fail => {
console.log(fail) // reject 接收失敗 // '失敗'
})
finally
來確認工作結束finally
不帶有任何參數,適合用來確認 Ajax 已經讀取完成promise(1)
.then(success => {
console.log(success)
}).finally(() => {
console.log('done')
})
參考文章:
]]>在學習同步與非同步的概念時很容易混淆,「同步」光看字面上的意思,可能會誤解成「所有動作同時進行」,而其實正好相反。
就以去夜市點餐來說明,如果今天想買的東西有:雞排、QQ球、珍奶。
同步 (sync):先去買雞排 -> 再去買QQ球 -> 然後再買珍奶。
像這樣「先完成 A 才能做 B、C、D ...」的運作方式我們就會把它稱作「同步」。
非同步 (async):再找兩個朋友幫忙,每人去一個攤位買一樣東西,就可以同時買好雞排、QQ球、珍奶。
所以同步的概念其實比較像是「一步一步來處理」的意思。
非同步則是,我不用等待 A 做完才做 B、C,而是這三個事情可以同時發送出去。
Promise 是 ES6 新增的建構函式,用來優化非同步的語法,增加可讀性。
Promise 字面上翻譯就是「約定、承諾」,可以想像成 A 和 B 約定要做某件事情,接著回報處理結果,而這個結果只能是「完成」或「拒絕」。
function asyncFunction (value) {
return new Promise((resolve, rejecte) => {
value ? resolve('處理完成的結果') : reject('被拒絕的原因')
})
}
JavaScript 是單執行緒(同步)的程式語言,一次只能處理一件事情,所以遇到非同步的事件時,就會先把程式碼放到「事件佇列」,等到所有事件處理完後才會執行非同步事件。
console.log('開始') // 執行順序 1
setTimeout(() => {
console.log('非同步事件') // 執行順序 3
}, 0)
console.log('程式碼結束') // 執行順序 2
Promise 在處理非同步的事件的過程中,包含不同的進度狀態
resolve
的結果reject
的結果Promise 可以用 then
和 catch
來接收並回傳結果:
then
可以同時接收成功、失敗結果,而 catch
只接收失敗結果
reject
時,都會直接跳到 catch
,之後的 then
都不會執行catch
依然可以使用 return
繼續串接,但很少這樣使用promise(1)
.then(success => {
console.log(success) // resolve 接收成功 // '1'
return promise(0) // return promise(0) -> reject 跳到 catch
})
.then(success => { // 因為上面出現 reject 所以跳過
console.log(success)
return promise(3)
})
.catch( fail => {
console.log(fail) // reject 接收失敗 // '失敗'
})
then
中的兩個函式必定執行其中一個,可以用此方式確保所有的鏈接都能夠被執行promise(0)
.then(success => {
console.log(success)
return promise(1)
}, fail => {
console.log(fail) // reject 接收失敗 // '失敗'
return promise(2) // return promise(2)
})
.then(success => {
console.log(success) // resolve 接收成功 // '2'
return promise(0) // return promise(0)
}, fail => {
console.log(fail)
return promise(4)
})
.then(success => {
console.log(success)
}, fail => {
console.log(fail) // reject 接收失敗 // '失敗'
})
finally
來確認工作結束finally
不帶有任何參數,適合用來確認 Ajax 已經讀取完成promise(1)
.then(success => {
console.log(success)
}).finally(() => {
console.log('done')
})
參考文章:
]]>Callback Function 和一般函式沒有什麼不同,差別在於被呼叫執行的時機。
JavaScript 是一個「事件驅動」的程式語言,也就是說每段程式都必須滿足一定的驅動條件,才會啟動執行程式。
function fn () {
console.log('This is a function')
}
// 如果沒有呼叫 fn,就不會執行函式
所謂的「Callback Function」其實就是「把函式當作參數,透過另外一個函式呼叫」。
可以解釋成:希望在某個 地點,透過某個 動作,執行某個 程式,來達成結果。
地點.addEventListener('動作', 參數)
我們可以用一段匿名函式的方式把函式帶入參數:
window.addEventListener('click', function () {
alert('This is callback function.')
})
也可以用具名函式的方式帶入:
function active () {
alert('This is callback function.')
}
window.addEventListener('click', active)
Callback Function 還有另外一個功能,就是「控制多個函式間執行的順序」。
function funcA () {
console.log('A')
}
function funcB () {
console.log('B')
}
funcA()
funcB()
範例中定義兩個函式,並且分別啟動 funcA
和 funcB
,此時兩個函式都會被立刻執行。
如果這時用非同步程式加上一段等待時間:
function funcA () {
window.setTimeout(function () {
console.log('A')
}, 1000)
}
function funcB () {
window.setTimeout(function () {
console.log('B')
}, 1000)
}
funcA()
funcB()
這樣就沒辦法確定 funcA
和 funcB
誰會先出現。
類似這種情況,為了確保執行順序,就會透過 Callback 的方式來處理。
function funcA (callback) {
window.setTimeout(function () {
console.log('A')
callback()
}, 1000)
}
function funcB () {
window.setTimeout(function () {
console.log('B')
}, 1000)
}
// 將 funcB 作為參數帶入 funcA
funcA(funcB)
如此一來就能確保,無論 funcA
的等待時間有多長,funcB
都會在 funcA
結束後才接著執行。
但是如果程式之間相依程度過深,就會造成傳說中的 波動拳,也就是「Callback Hell」,讓程式難以維護。
Callback Function 和一般函式沒有什麼不同,差別在於被呼叫執行的時機。
JavaScript 是一個「事件驅動」的程式語言,也就是說每段程式都必須滿足一定的驅動條件,才會啟動執行程式。
function fn () {
console.log('This is a function')
}
// 如果沒有呼叫 fn,就不會執行函式
所謂的「Callback Function」其實就是「把函式當作參數,透過另外一個函式呼叫」。
可以解釋成:希望在某個 地點,透過某個 動作,執行某個 程式,來達成結果。
地點.addEventListener('動作', 參數)
我們可以用一段匿名函式的方式把函式帶入參數:
window.addEventListener('click', function () {
alert('This is callback function.')
})
也可以用具名函式的方式帶入:
function active () {
alert('This is callback function.')
}
window.addEventListener('click', active)
Callback Function 還有另外一個功能,就是「控制多個函式間執行的順序」。
function funcA () {
console.log('A')
}
function funcB () {
console.log('B')
}
funcA()
funcB()
範例中定義兩個函式,並且分別啟動 funcA
和 funcB
,此時兩個函式都會被立刻執行。
如果這時用非同步程式加上一段等待時間:
function funcA () {
window.setTimeout(function () {
console.log('A')
}, 1000)
}
function funcB () {
window.setTimeout(function () {
console.log('B')
}, 1000)
}
funcA()
funcB()
這樣就沒辦法確定 funcA
和 funcB
誰會先出現。
類似這種情況,為了確保執行順序,就會透過 Callback 的方式來處理。
function funcA (callback) {
window.setTimeout(function () {
console.log('A')
callback()
}, 1000)
}
function funcB () {
window.setTimeout(function () {
console.log('B')
}, 1000)
}
// 將 funcB 作為參數帶入 funcA
funcA(funcB)
如此一來就能確保,無論 funcA
的等待時間有多長,funcB
都會在 funcA
結束後才接著執行。
但是如果程式之間相依程度過深,就會造成傳說中的 波動拳,也就是「Callback Hell」,讓程式難以維護。
我們都知道閉包,但什麼是必包?主要功能是什麼呢?
先從字面上拆解,Closure 可以拆成 Close 和 -ure 字尾,-ure 接在動作的字根後面,表示動作本身、動作造成的結果、或是執行動作的人事物。
所以閉包本身就是一個關閉的動作,把程式碼片段關閉在一個函式內,限制作用域,建立自己的環境與外界隔絕。
用一個錢包範例先來比較一下差異:
假設每個錢包都有1000元,大明花了300元,小明花了500元,計算每個人錢包裡的餘額
var balance = 1000
function wallet (cost) {
return balance = balance - cost
}
var MING = wallet(300)
var ming = wallet(500)
console.log(MING, ming) // 700 200
快速計算一下,大明錢包裡應該要有700元,小明應該還剩500元,但是結果小明卻是剩下200元,難道大明偷拿小明錢包裡的錢嗎?
一切都是誤會呀!因為現在沒有限制作用域,目前還是兩個人共用同一個錢包的狀態,如果想要讓他們他們各自花各自的錢,就要給他們一人一個錢包。
var balanceA = 1000
var balanceB = 1000
function walletA (cost) {
return balanceA = balanceA - cost
}
function walletB (cost) {
return balanceB = balanceB - cost
}
var MING = walletA(300)
var ming = walletB(500)
console.log(MING, ming) // 700 500
如果今天想要再計算別的錢包,就要再重複寫一次,這樣就會變成一樣的事情重複寫,不但看起來很複雜,也很沒有效率。這個時候就可以加入閉包限制作用域,建立一個「函式工廠」。
讓每個功能相同的 function,對應到每個獨立的環境變數,且互不相干擾。
function wallet(cost) {
var balance = 1000
function counter () {
balance = balance - cost
console.log(balance)
}
counter()
}
var userA = wallet(300) // 700
var userB = wallet(500) // 500
var userC = wallet(200) // 800
這時候就可以讓每個人都可以獨立計算錢包餘額,也可以加入變數,一開始分別放入不同金額,管理各自的錢包。
function wallet(initBalance, cost) {
var balance = initBalance
function counter () {
balance = balance - cost
console.log(balance)
}
counter()
}
var userA = wallet(1000, 300) // 700
var userB = wallet(2000, 500) // 1500
var userC = wallet(1500, 200) // 1300
參考文章:
]]>我們都知道閉包,但什麼是必包?主要功能是什麼呢?
先從字面上拆解,Closure 可以拆成 Close 和 -ure 字尾,-ure 接在動作的字根後面,表示動作本身、動作造成的結果、或是執行動作的人事物。
所以閉包本身就是一個關閉的動作,把程式碼片段關閉在一個函式內,限制作用域,建立自己的環境與外界隔絕。
用一個錢包範例先來比較一下差異:
假設每個錢包都有1000元,大明花了300元,小明花了500元,計算每個人錢包裡的餘額
var balance = 1000
function wallet (cost) {
return balance = balance - cost
}
var MING = wallet(300)
var ming = wallet(500)
console.log(MING, ming) // 700 200
快速計算一下,大明錢包裡應該要有700元,小明應該還剩500元,但是結果小明卻是剩下200元,難道大明偷拿小明錢包裡的錢嗎?
一切都是誤會呀!因為現在沒有限制作用域,目前還是兩個人共用同一個錢包的狀態,如果想要讓他們他們各自花各自的錢,就要給他們一人一個錢包。
var balanceA = 1000
var balanceB = 1000
function walletA (cost) {
return balanceA = balanceA - cost
}
function walletB (cost) {
return balanceB = balanceB - cost
}
var MING = walletA(300)
var ming = walletB(500)
console.log(MING, ming) // 700 500
如果今天想要再計算別的錢包,就要再重複寫一次,這樣就會變成一樣的事情重複寫,不但看起來很複雜,也很沒有效率。這個時候就可以加入閉包限制作用域,建立一個「函式工廠」。
讓每個功能相同的 function,對應到每個獨立的環境變數,且互不相干擾。
function wallet(cost) {
var balance = 1000
function counter () {
balance = balance - cost
console.log(balance)
}
counter()
}
var userA = wallet(300) // 700
var userB = wallet(500) // 500
var userC = wallet(200) // 800
這時候就可以讓每個人都可以獨立計算錢包餘額,也可以加入變數,一開始分別放入不同金額,管理各自的錢包。
function wallet(initBalance, cost) {
var balance = initBalance
function counter () {
balance = balance - cost
console.log(balance)
}
counter()
}
var userA = wallet(1000, 300) // 700
var userB = wallet(2000, 500) // 1500
var userC = wallet(1500, 200) // 1300
參考文章:
]]>具名函式顧名思義必須要有函式名稱。
而具名函式能夠在函式內被調用,但是函式中的具名函式無法在函式以外被調用。
function sayHello () {
function inner () {
return console.log('inner')
}
return console.log('Hello!')
}
function callSayHello () {
sayHello() // 具名函式能夠在函式內被調用
}
callSayHello() // Hello!
inner() // inner is not defined 無法被調用
函式名稱是可以被忽略的,也就是匿名函式,通常會用表達式來宣告匿名函式,另外立即函式(IIFE)也是屬於匿名函式。
var sayHello = function () {
console.log('Hello!')
;(function () {
console.log('Hey!')
})()
}
sayHello()
// Hello!
// Hey!
// 函式陳述式、具名函式
function fn() { ... }
// 函式表達式、匿名函式
var fn = function() { ... }
函式有屬於自己的作用域,在 ES6 之前,「JavaScript 切分變數有效範圍的最小單位是 function」,所以在這個範圍內的變數只屬於這個函式,一旦離開函式範圍,記憶體就會被釋放掉。
var a = 3
function fn () {
var a = 6
var b = 10
}
fn()
console.log(a) // a = 3
console.log(b) // b is not defined
如果在函式裡面沒有重新宣告變數,那 function 就會向外一層一層找。
function 可以從內層讀取外層宣告的變數,但是外層沒辦法讀取內層宣告的變數,這種行為稱作「範圍鏈(Scope Chain)」。
var msg = 'global'
function outer() {
var msg = 'local'
function inner () {
return msg
}
return inner
}
var innerFunc = outer()
var result = innerFunc()
console.log(result) // local
範例中加了一段 var innerFunc = outer()
,呼叫 outer
回傳的 inner
結果,取得原本從外層無法讀取的 inner
,看似好像把 inner
獨立出來,msg 向外查找所以結果是的外層 global
。
但有個重要觀念:範圍鏈是在函式被定義當下決定的,不是被呼叫的時候。
所以在「定義函式之後,呼叫函式之前」範圍鏈就已經被建立,msg
為 inner
外層的 local
。
具名函式顧名思義必須要有函式名稱。
而具名函式能夠在函式內被調用,但是函式中的具名函式無法在函式以外被調用。
function sayHello () {
function inner () {
return console.log('inner')
}
return console.log('Hello!')
}
function callSayHello () {
sayHello() // 具名函式能夠在函式內被調用
}
callSayHello() // Hello!
inner() // inner is not defined 無法被調用
函式名稱是可以被忽略的,也就是匿名函式,通常會用表達式來宣告匿名函式,另外立即函式(IIFE)也是屬於匿名函式。
var sayHello = function () {
console.log('Hello!')
;(function () {
console.log('Hey!')
})()
}
sayHello()
// Hello!
// Hey!
// 函式陳述式、具名函式
function fn() { ... }
// 函式表達式、匿名函式
var fn = function() { ... }
函式有屬於自己的作用域,在 ES6 之前,「JavaScript 切分變數有效範圍的最小單位是 function」,所以在這個範圍內的變數只屬於這個函式,一旦離開函式範圍,記憶體就會被釋放掉。
var a = 3
function fn () {
var a = 6
var b = 10
}
fn()
console.log(a) // a = 3
console.log(b) // b is not defined
如果在函式裡面沒有重新宣告變數,那 function 就會向外一層一層找。
function 可以從內層讀取外層宣告的變數,但是外層沒辦法讀取內層宣告的變數,這種行為稱作「範圍鏈(Scope Chain)」。
var msg = 'global'
function outer() {
var msg = 'local'
function inner () {
return msg
}
return inner
}
var innerFunc = outer()
var result = innerFunc()
console.log(result) // local
範例中加了一段 var innerFunc = outer()
,呼叫 outer
回傳的 inner
結果,取得原本從外層無法讀取的 inner
,看似好像把 inner
獨立出來,msg 向外查找所以結果是的外層 global
。
但有個重要觀念:範圍鏈是在函式被定義當下決定的,不是被呼叫的時候。
所以在「定義函式之後,呼叫函式之前」範圍鏈就已經被建立,msg
為 inner
外層的 local
。
var
的宣告屬於 全域變數,代表 全域物件的屬性。
for (var i=0; i<3; i++) {}
console.log(i) // 3
假設一個 for
迴圈用 var
宣告 i
,在全域還是可以取得 i
值。
如果想要限制 i
的作用域的話,可以使用立即函式,避免外層取到 for
迴圈裡面的值,最直接的方式就是使用 let
宣告。
(function () {
for (var i=0; i<3; i++) {}
})()
console.log(i) // i is not defined
---
for (let i=0; i<3; i++) {}
console.log(i) // i is not defined
在 JavaScript 中,var
的作用域是函式作用域,但在函式內的區域變數也可能被視為全域變數,傳統用 var
宣告變數可能會汙染全域,如果使用 let
、const
就可以避免這種情形。
if (true) {
var a = 10
}
console.log(a) // 10
console.log(window.a) // 10
let
的作用域屬於區塊內({}
大括號),和 const
最大差異就是可以重新賦值,而 const
不行。
let a = 0
{
let a = 1 // 因為 let 的作用域是區塊內,所以不算重複宣告
a = 2
console.log(a) // 2
}
console.log(0) // 0
舉個非同步 for
迴圈的例子,假設想要讓 setTimeout
依序執行並印出結果,如果用 var
宣告的話,因為 var
是全域變數,所以只會取出最後一次執行的結果。
而 setTimeout
是非同步程式,會先放到「事件佇列」裡面,等到所有程式碼執行完畢後才會執行。
如果想要達到預期結果,只要用 let
宣告就可以依序執行並取得正確數值。
for (var i=0; i<3; i++) {
setTimeout(function () {
console.log(`這是第 ${i} 次執行結果`)
}, 0)
} // 這是第 3 次執行結果
---
for (let i=0; i<3; i++) {
setTimeout(function () {
console.log(`這是第 ${i + 1} 次執行結果`)
}, 0)
}
// 這是第 0 次執行結果
// 這是第 1 次執行結果
// 這是第 2 次執行結果
const
是宣告一個常數,用 const
宣告的變數沒辦法再被調整。
雖然變數沒辦法被調整,但如果是物件的話,因為物件屬於傳參考特性,只要不直接替換掉物件,可以修改內容屬性值。
const person = {
name: '小明',
money: 500
}
person.name = '大明'
console.log(person.name) // 大明
---
person = {}
console.log(person) // Assignment to constant variable.
JavaScript 中有 hoisting 的現象,先來比較一下這三種的狀況:
console.log(a) // undefined
var a = 1
---
console.log(b) // b is not defined
let b = 2
---
console.log(c) // Cannot access 'c' before initialization
const c = 3
var
: undefined
,有 hoistinglet
: not defined
,沒有 hoistingconst
: 宣告前無法使用,沒有 hoisting看起來沒什麼問題,接著猜測一下面範例結果:
let
沒有提升的話,就會往外層查找取得 小明 的值let
有提升的話,就會出現其他狀況let Ming = '小明'
{
console.log(Ming)
let Ming = '大明'
}
結果是(2): Cannot access 'Ming' before initialization
let
在提升的過程中會產生一個「暫時性死區」Temporal Dead Zone(TDZ)。
在這個區域內是無法存取變數,也就不會賦予 undefined
的值,如果試圖取值就會跳出錯誤提示。
所以 let
雖然有類似提升 創造 - 執行階段 的概念,但是和 var
的 hoisting 概念並不相同。
let Ming = '小明'
{
// 創造
let Ming // 暫時性死區 TDZ
// 執行
console.log(Ming)
let Ming = '大明'
}
作用範圍 | 重複宣告 | 重新賦值 | Hoisting | |
---|---|---|---|---|
var | 全域 | v | v | v |
let | 區塊 | x | v | v |
const | 區塊 | x | x | x |
var
的宣告屬於 全域變數,代表 全域物件的屬性。
for (var i=0; i<3; i++) {}
console.log(i) // 3
假設一個 for
迴圈用 var
宣告 i
,在全域還是可以取得 i
值。
如果想要限制 i
的作用域的話,可以使用立即函式,避免外層取到 for
迴圈裡面的值,最直接的方式就是使用 let
宣告。
(function () {
for (var i=0; i<3; i++) {}
})()
console.log(i) // i is not defined
---
for (let i=0; i<3; i++) {}
console.log(i) // i is not defined
在 JavaScript 中,var
的作用域是函式作用域,但在函式內的區域變數也可能被視為全域變數,傳統用 var
宣告變數可能會汙染全域,如果使用 let
、const
就可以避免這種情形。
if (true) {
var a = 10
}
console.log(a) // 10
console.log(window.a) // 10
let
的作用域屬於區塊內({}
大括號),和 const
最大差異就是可以重新賦值,而 const
不行。
let a = 0
{
let a = 1 // 因為 let 的作用域是區塊內,所以不算重複宣告
a = 2
console.log(a) // 2
}
console.log(0) // 0
舉個非同步 for
迴圈的例子,假設想要讓 setTimeout
依序執行並印出結果,如果用 var
宣告的話,因為 var
是全域變數,所以只會取出最後一次執行的結果。
而 setTimeout
是非同步程式,會先放到「事件佇列」裡面,等到所有程式碼執行完畢後才會執行。
如果想要達到預期結果,只要用 let
宣告就可以依序執行並取得正確數值。
for (var i=0; i<3; i++) {
setTimeout(function () {
console.log(`這是第 ${i} 次執行結果`)
}, 0)
} // 這是第 3 次執行結果
---
for (let i=0; i<3; i++) {
setTimeout(function () {
console.log(`這是第 ${i + 1} 次執行結果`)
}, 0)
}
// 這是第 0 次執行結果
// 這是第 1 次執行結果
// 這是第 2 次執行結果
const
是宣告一個常數,用 const
宣告的變數沒辦法再被調整。
雖然變數沒辦法被調整,但如果是物件的話,因為物件屬於傳參考特性,只要不直接替換掉物件,可以修改內容屬性值。
const person = {
name: '小明',
money: 500
}
person.name = '大明'
console.log(person.name) // 大明
---
person = {}
console.log(person) // Assignment to constant variable.
JavaScript 中有 hoisting 的現象,先來比較一下這三種的狀況:
console.log(a) // undefined
var a = 1
---
console.log(b) // b is not defined
let b = 2
---
console.log(c) // Cannot access 'c' before initialization
const c = 3
var
: undefined
,有 hoistinglet
: not defined
,沒有 hoistingconst
: 宣告前無法使用,沒有 hoisting看起來沒什麼問題,接著猜測一下面範例結果:
let
沒有提升的話,就會往外層查找取得 小明 的值let
有提升的話,就會出現其他狀況let Ming = '小明'
{
console.log(Ming)
let Ming = '大明'
}
結果是(2): Cannot access 'Ming' before initialization
let
在提升的過程中會產生一個「暫時性死區」Temporal Dead Zone(TDZ)。
在這個區域內是無法存取變數,也就不會賦予 undefined
的值,如果試圖取值就會跳出錯誤提示。
所以 let
雖然有類似提升 創造 - 執行階段 的概念,但是和 var
的 hoisting 概念並不相同。
let Ming = '小明'
{
// 創造
let Ming // 暫時性死區 TDZ
// 執行
console.log(Ming)
let Ming = '大明'
}
作用範圍 | 重複宣告 | 重新賦值 | Hoisting | |
---|---|---|---|---|
var | 全域 | v | v | v |
let | 區塊 | x | v | v |
const | 區塊 | x | x | x |
一般來說,我們都會事先宣告變數,接著才會執行。
如果沒有宣告就執行的話,會得到 ReferenceError: a is not defined
的錯誤,因為 JavaScript 找不到這個名為 a
的變數。
console.log(a) // 'a is not defined'
假設一段程式在執行後接著宣告變數,依照上面敘述答案應該是 a is not defined
,但是結果卻是 undefined
,Why ?
console.log(a) // undefined
var a
JavaScript 的運行分為「創造階段」和「執行階段」。
在創造環境時,會將宣告的變數拉升到最上面,這個現象就叫做提升。
雖然理解上是移動到最上面,實際上位置並沒有改變,也可以說是靠「想像」把變數移動到最上面,而移動的行為就是把變數存進記憶體裡。
要特別注意變數宣告和賦值不同,雖然提升了宣告,但賦值卻會留在原地「依序」等待被執行。
console.log(a)
var a = 2
創造階段:
var a
執行階段:
console.log(a) // undefined
a = 2
除了變數宣告外,「函式陳述式」也會被提升到創造階段,並且優先載入。
function fn () {
console.log('apple')
}
fn()
function fn () {
console.log('orange')
}
fn()
拆解:
創造階段:函式優先
function fn () {
console.log('apple')
}
function fn () {
console.log('orange')
}
執行階段:
fn() // 第一次執行:orange
fn() // 第二次執行:orange
如果是「函式表達式」又是另外一種狀況:在執行階段,才會把函式內容指派給變數。
var fn = function () {
console.log('小明')
}
fn()
function fn () {
console.log('orange')
}
fn()
拆解:
創造階段:函式優先
function () {
console.log('apple')
}
function fn () {
console.log('orange')
}
var fn
執行階段:
fn = function () {
console.log('apple')
}
fn() // 第一次執行:apple
fn() // 第二次執行:apple
Hoistion 最初的設計是為了實現函式互相傳遞,畢竟在開發時會不只有一個函式,如果每個函式都要事先宣告的話,不僅繁瑣而且不好使用,如果有 Hoisting 的話就可以在任何地方呼叫函示。
一般來說,我們都會事先宣告變數,接著才會執行。
如果沒有宣告就執行的話,會得到 ReferenceError: a is not defined
的錯誤,因為 JavaScript 找不到這個名為 a
的變數。
console.log(a) // 'a is not defined'
假設一段程式在執行後接著宣告變數,依照上面敘述答案應該是 a is not defined
,但是結果卻是 undefined
,Why ?
console.log(a) // undefined
var a
JavaScript 的運行分為「創造階段」和「執行階段」。
在創造環境時,會將宣告的變數拉升到最上面,這個現象就叫做提升。
雖然理解上是移動到最上面,實際上位置並沒有改變,也可以說是靠「想像」把變數移動到最上面,而移動的行為就是把變數存進記憶體裡。
要特別注意變數宣告和賦值不同,雖然提升了宣告,但賦值卻會留在原地「依序」等待被執行。
console.log(a)
var a = 2
創造階段:
var a
執行階段:
console.log(a) // undefined
a = 2
除了變數宣告外,「函式陳述式」也會被提升到創造階段,並且優先載入。
function fn () {
console.log('apple')
}
fn()
function fn () {
console.log('orange')
}
fn()
拆解:
創造階段:函式優先
function fn () {
console.log('apple')
}
function fn () {
console.log('orange')
}
執行階段:
fn() // 第一次執行:orange
fn() // 第二次執行:orange
如果是「函式表達式」又是另外一種狀況:在執行階段,才會把函式內容指派給變數。
var fn = function () {
console.log('小明')
}
fn()
function fn () {
console.log('orange')
}
fn()
拆解:
創造階段:函式優先
function () {
console.log('apple')
}
function fn () {
console.log('orange')
}
var fn
執行階段:
fn = function () {
console.log('apple')
}
fn() // 第一次執行:apple
fn() // 第二次執行:apple
Hoistion 最初的設計是為了實現函式互相傳遞,畢竟在開發時會不只有一個函式,如果每個函式都要事先宣告的話,不僅繁瑣而且不好使用,如果有 Hoisting 的話就可以在任何地方呼叫函示。