併發(concurrency)和並行(parallelism)區別
併發是宏觀概念,我分別有任務 A 和任務 B,在一段時間內通過任務間的切換完成了這兩個任務,這種情況就可以稱之爲併發。
並行是微觀概念,假設 CPU 中存在兩個核心,那麼我就可以同時完成任務 A、B。同時完成多個任務的情況就可以稱之爲並行。
回調函數(Callback)
回調函數有一個致命的弱點,就是容易寫出回調地獄(Callback hell)
ajax(url, () => {
// 處理邏輯
ajax(url1, () => {
// 處理邏輯
ajax(url2, () => {
// 處理邏輯
})
})
})
回調地獄的根本問題就是:
- 嵌套函數存在耦合性,一旦有所改動,就會牽一髮而動全身
- 嵌套函數一多,就很難處理錯誤
回調函數還存在着別的幾個缺點,比如不能使用 try catch 捕獲錯誤,不能直接 return。
Generator
Generator 最大的特點就是可以控制函數的執行。
function *foo(x) {
let y = 2 * (yield (x + 1))
let z = yield (y / 3)
return (x + y + z)
}
let it = foo(5)
console.log(it.next()) // => {value: 6, done: false}
console.log(it.next(12)) // => {value: 8, done: false}
console.log(it.next(13)) // => {value: 42, done: true}
- 首先 Generator 函數調用和普通函數不同,它會返回一個迭代器
- 當執行第一次 next 時,傳參會被忽略,並且函數暫停在 yield (x + 1) 處,所以返回 5 + 1 = 6
- 當執行第二次 next 時,傳入的參數等於上一個 yield 的返回值,如果你不傳參,yield 永遠返回 undefined。此時 let y = 2 * 12,所以第二個 yield 等於 2 * 12 / 3 = 8
- 當執行第三次 next 時,傳入的參數會傳遞給 z,所以 z = 13, x = 5, y = 24,相加等於 42
Generator 函數一般見到的不多,其實也於他有點繞有關係,並且一般會配合 co 庫去使用。當然,我們可以通過 Generator 函數解決回調地獄的問題。
function *fetch() {
yield ajax(url, () => {})
yield ajax(url1, () => {})
yield ajax(url2, () => {})
}
let it = fetch()
let result1 = it.next()
let result2 = it.next()
let result3 = it.next()
Promise
Promise的作用
Promise 翻譯過來就是承諾的意思,這個承諾會在未來有一個確切的答覆,並且該承諾有三種狀態,分別是: 等待中(pending);完成了 (resolved);拒絕了(rejected)。
這個承諾一旦從等待狀態變成爲其他狀態就永遠不能更改狀態了,也就是說一旦狀態變爲 resolved 後,就不能再次改變。
當我們在構造 Promise 的時候,構造函數內部的代碼是立即執行的。
Promise 實現了鏈式調用,也就是說每次調用 then 之後返回的都是一個 Promise,並且是一個全新的 Promise,原因也是因爲狀態不可變。如果你在 then 中 使用了 return,那麼 return 的值會被 Promise.resolve() 包裝。
Promise.resolve(1)
.then(res => {
console.log(res) // => 1
return 2 // 包裝成 Promise.resolve(2)
})
.then(res => {
console.log(res) // => 2
})
Promise 也很好地解決了回調地獄的問題
ajax(url)
.then(res => {
console.log(res)
return ajax(url1)
}).then(res => {
console.log(res)
return ajax(url2)
}).then(res => console.log(res))
async 及 await
一個函數如果加上 async ,那麼該函數就會返回一個 Promise
async function test() {
return "1"
}
console.log(test()) // -> Promise {<resolved>: "1"}
async 就是將函數返回值使用 Promise.resolve() 包裹了下,和 then 中處理返回值一樣,並且 await 只能配套 async 使用
async function test() {
let value = await sleep()
}
async 和 await 可以說是異步終極解決方案了,相比直接使用 Promise 來說,優勢在於處理 then 的調用鏈,能夠更清晰準確的寫出代碼,畢竟寫一大堆 then 也很噁心,並且也能優雅地解決回調地獄問題。當然也存在一些缺點,因爲 await 將異步代碼改造成了同步代碼,如果多個異步代碼沒有依賴性卻使用了 await 會導致性能上的降低。
舉例:
let a = 0
a++
console.log('1', a) // -> '1' 1
b(a)//執行b函數
async function b (num){
await c(num)//將異步變爲同步,所以會提前執行c函數,再執行num++
num++
console.log('3', num) // -> '3' 2
}
function c (num){
num = num + 10
console.log('2', num) // -> '2' 11
}
await 內部實現了 generator,其實 await 就是 generator 加上 Promise 的語法糖,且內部實現了自動執行 generator。
常用定時器函數
- setTimeout 定時器
- setInterval 計時器
- requestAnimationFrame
有循環定時器的需求,其實完全可以通過 requestAnimationFrame 來實現。首先 requestAnimationFrame 自帶函數節流功能,基本可以保證在 16.6 毫秒內只執行一次(不掉幀的情況下),並且該函數的延時效果是精確的,沒有其他定時器時間不準的問題。
function setInterval(callback, interval) {
let timer
const now = Date.now
let startTime = now()
let endTime = startTime
const loop = () => {
timer = window.requestAnimationFrame(loop)
endTime = now()
if (endTime - startTime >= interval) {
startTime = endTime = now()
callback(timer)
}
}
timer = window.requestAnimationFrame(loop)
return timer
}
let a = 0
setInterval(timer => {
console.log(1)
a++
if (a === 3) cancelAnimationFrame(timer)
}, 1000)