EventLoop 的理解

通常前端了解eventloop的時候, 最想知道代碼執行的前後順序,並不是分析EventLoop。因此這裏先說總結。 爲何?由於面試常考這個😂,由於分析和聽懂分析都費勁。javascript

##1、總結html

  1. js 單線程缺點,容易出現"假死"(如alert()以後,dom不渲染)。優勢:保證 dom 的渲染不易出錯。
  2. 解決"假死"的問題?其餘語言,如java採用多線程去解決,避免佔用計算機空間太大,dom 渲染問題易出錯問題存在。因此js 採用單線程+異步解決方案。
  3. 單線程 + 異步的實現方式叫作——EventLoop
  4. 加入異步隊列方式有兩種,第一種是宏任務;第二種是微任務。微任務加入異步隊列的優先級宏任務,且是先進先出原則,說得比較繞,簡而言之就是,微任務執行完,纔會執行宏任務
  5. 微任務:Promise.prototype.then、async await、Process.nextTick(Node獨有)、Object.observe(廢棄)、MutationObserver
  6. 宏任務:script所有代碼、setTimeout、setInterval、setImmediate(瀏覽器暫時不支持,只有IE10支持,具體可見MDN)、I/O、UI Rendering。
  7. 執行的優先級即,同步函數(主線程上) -> 異步隊列·微任務 -> 異步隊列·宏任務

ps: 對純前端而言,掌握Promise.prototype.then、async await 和 setTimeout 的區別就能夠了。另外,Promise、async await是能夠轉換的,可是瀏覽器版本問題,async await 的優先級可能高於 Promise(能夠忽略這種狀況)。前端

測試題目:java

const fn1 = await function () {
	await fn2()
	console.log(1)
}

async function fn2 () {
	await console.log(2)
}

fn1()

setTimeout(function () {
	console.log(3)
})

new Promise(function (resolve, reject) {
	console.log(4)
	resovle()
}).then(function () {
	console.log(5)
}).then(function () {
	console.log(6)
})
// 答案
// 2
// 4
// 1
// 5
// 6
// 3
複製代碼

分析:git

  1. 2 和 4 是同步函數
  2. 一、5 和 6 是微任務,異步隊列的順序是一、五、6
  3. 3 是宏任務

代碼轉換分析github

function fn1 () {
	new Promise(function(resolve, reject) {
		console.log(2)
		resolve()
	}).then(function () {
		console.log(1)
	})
}

fn1()

setTimeout(function () {
	console.log(3)
})

new Promise(function (resolve) {
	console.log(4)
	resolve()
}).then(function () {
	console.log(5)
}).then(function () {
	console.log(6)
})
複製代碼

2、內存分析和原因

阮一峯eventloop www.ruanyifeng.com/blog/2013/1… 看內存分析的這篇 github.com/baiyuze/not…面試

瞭解過,能夠跳過設計模式

3、封裝Promise源碼分析

看這篇:juejin.im/entry/59996… 以爲分析有點囉嗦,沒有提取關鍵信息promise

瞭解過,能夠跳過瀏覽器

4、uml 類圖分析Promise

MDN文檔 的 Promise 執行流程圖:

第 1 步 用到的設計模式 和 理解 promise 的簡易結構

瞭解代碼第一步,必定要知道他的設計模式,可以節約至關看代碼時間。Promise 代碼 最主要的模式,觀察者模式

封裝一個簡易的Promise的 resolvethen,便於理解 Pomise 的封裝解構

  • then 從語法結構上講是而後的意思,但在封裝上,是將函數加入異步隊列,並返回一個 Promise 的一個相似對象
  • resolve 執行異步隊列
class Que {
  _queueLit = []
  constructor (handler) {
    handler.call(this, this._resolve)
  }
  _queue (cb) {
    setTimeout(cb)
  }
  _resolve = () => {
    const { _queueLit, _queue } = this
    const resolve = function () {
      let cb
      while (cb = _queueLit.shift()) {
        cb()
      }
    }
    _queue(resolve)
  }
  then (cb) {
    this._queueLit.push(cb)
    return this
  }
}

// test
setTimeout(() => {
  console.log(4) 
});
new Que(function (resolve) {
  console.log(1)
  resolve()
}).then(function () {
  console.log(2)
}).then(function () {
  console.log(3)
})

// 結果
// 1
// 4
// 2
// 3
複製代碼

後記:否則發現 setTimeout 執行的 callback 爲何是全局的,以及 await 爲何不能在全局環境下,只能在函數內?緣由是爲了加入異步隊列

第 2 步 promise 處理8關鍵點(第 3 點 最爲重要)

上面的封裝的代碼,簡單理解 Promise 封裝的解構,如今梳理 Promise的解構,可知道 promise 的根本的兩個方法: then 和 _resolve , _queues、_status。reject 、catch等都是在此基礎上上進行二次封裝。下列數列梳理幾個點:

  1. then 接受的函數,是 Promise 時的處理方式
  2. then 接受的函數,是同步函數時的處理方式
  3. then 返回一個 Promise 實例,將該實例要執行的 _resolve 放到上一個 Promise 實例的異步隊列中即將執行的異步函數中
  4. _resolve 傳遞參數的value 是一個Promise 時的處理方式
  5. _resolve 放到 eventloop 中,即 setTimeout(run)中 ,先進先出執行異步隊列
  6. _status 狀態改變只在 _resolve 中改變
  7. _status 狀態判斷只在then中執行
  8. 利用自責鏈以後,每一個Promise 的 _queues 只有一個元素, _queues 是裏面是多個觀察者,這裏根據其餘人說的,要實現一個 1 對 1 的觀察者模式。

第 3 步 從完整版的Promise提取關鍵代碼

源碼地址--> coderlt.coding.me/2016/12/04/…

uml 類圖

拿到源碼,將源碼錯誤檢測、不須要分析的方法統統幹掉,避免混淆視聽,獲得以下代碼:

// 判斷變量否爲function
const isFunction = variable => typeof variable === 'function'

const StatusType = {
  PENDING: 'PENDING',
  FULFILLED: 'FULFILLED',
}

class MyPromise {
  _status = StatusType.PENDING // 添加狀態
  _value = undefined // 添加狀態
  _fulfilledQueues = [] // 添加成功回調函數隊列
  constructor(handle) {
    handle(this._resolve.bind(this))
  }
  _resolve(val) {
    const run = () => {
      if (this._status !== StatusType.PENDING) return
      const runFulfilled = (value) => {
        let cb
        while (cb = this._fulfilledQueues.shift()) {
          cb(value)
        }
      }
      if (val instanceof MyPromise) {
        const resolvePromise = val
        resolvePromise.then(value => {
          this._value = value
          this._status = StatusType.FULFILLED
          runFulfilled(value)
        })
      } else {
        this._value = val
        this._status = StatusType.FULFILLED
        runFulfilled(val)
      }
    }
    setTimeout(run)
  }

  then(onFulfilled) {
    const {
      _value,
      _status
    } = this
    // 返回一個新的Promise對象
    return new MyPromise((onFulfilledNext) => {
      // 封裝一個成功時執行的函數
      let fulfilled = value => {
        let res = onFulfilled(value)
        if (res instanceof MyPromise) {
          res.then(onFulfilledNext)
        } else {
          // 下一個 promise 的 resolve 方法的執行
          onFulfilledNext(res)
        }
      }
      switch (_status) {
        case StatusType.PENDING:
          /* 相當重要的代碼 */
          this._fulfilledQueues.push(fulfilled)
           /* 相當重要的代碼 end */
          break
        case StatusType.FULFILLED:
          fulfilled(_value)
          break
      }
    })
  }
}
複製代碼

5、promise 和 async await

怎麼理解,特簡單,不用想那麼負責。就當 aysnc await 其實就是 promise 的語法糖。

驗證:

function a () {
  console.log('a')
  return 'a'
}

async function b () {
  const res = await a('a')
  return res
}

b().then(res => {
  console.log('this is', res)
})

const p = Promise.resolve(b)
console.log('b() is promise', b() instanceof Promise)
console.log('p is promise', p instanceof Promise)
// b() is promise true
// p is promise true
// this is a
複製代碼

總結

分析到這裏,基本瞭解的 promise.then 和 resolve 的實現。reject 至關於 promise 的 resolve 的翻版,catch、all、race 就不在話下,簡而言之,promise 源碼最終重要的封裝是 promise.then 和 resolve.

相關文章
相關標籤/搜索