<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          精進(jìn) JavaScript | 這些手寫你都會(huì)嗎 ?

          共 26918字,需瀏覽 54分鐘

           ·

          2022-11-01 13:42

          大廠技術(shù)  高級(jí)前端  Node進(jìn)階

          點(diǎn)擊上方 程序員成長(zhǎng)指北,關(guān)注公眾號(hào)

          回復(fù)1,加入高級(jí)Node交流群

          前言

          不知道今年大家有沒有感受到來自互聯(lián)網(wǎng)的“寒氣”,至少我是感受到了,面試的時(shí)候手寫代碼時(shí)很常見很常見的事情了。有時(shí)候沒遇到過還真一時(shí)半會(huì)寫不出來,企業(yè)招聘的要求也是越來越高,尤其是一些大廠會(huì)對(duì) JS 的功底有著更加苛刻的要求,所以學(xué)會(huì)手寫常見的 JS 模塊好像已經(jīng)快變?yōu)橐粋€(gè)基本技能了,也慢慢變?yōu)槲覀兪謱?webpack 手寫 mini-vue 的一個(gè) coding 基礎(chǔ) 了。當(dāng)然我們也不完全是為了去準(zhǔn)備面試而去學(xué)習(xí)這些常見模塊。死磕這些難啃的骨頭之后,你會(huì)從中學(xué)到很多優(yōu)秀的思想,對(duì)你的職業(yè)生涯也是很有幫助的。而且閱讀代碼本身就是一個(gè)很好的習(xí)慣,讀懂并且理解寫代碼人的思維邏輯更加重要。

          本文中涉及到的手寫模塊,大多數(shù)都是從網(wǎng)上的博客、面筋、??途W(wǎng)以及自己的面試經(jīng)驗(yàn)借鑒而來的。希望能對(duì)你有個(gè)幫助。

          基礎(chǔ)手寫

          全排列(力扣原題)

          要求以數(shù)組的形式返回字符串參數(shù)的所有排列組合。

          注意:

          1. 字符串參數(shù)中的字符無重復(fù)且僅包含小寫字母
          2. 返回的排列組合數(shù)組不區(qū)分順序
          const _permute = string => {
            const result = []
            const map = new Map()
            const dfs = (path) => {
              if (path.length === string.length) {
                result.push(path)
                return
              }
              for (let i = 0; i < string.length; i++) {
                if (map.get(string[i])) continue
                map.set(string[i], true)
                path += string[i]
                dfs(path)
                path = path.substring(0path.length - 1)
                map.set(string[i], false)
              }
            }
            dfs('')
            return result
          }
          console.log(_permute('abc')) // [ 'abc''acb''bac''bca''cab''cba' ]
          復(fù)制代碼

          instanceof

          • 如果 target 為基本數(shù)據(jù)類型直接返回 false
          • 判斷 Fn.prototype 是否在 target 的隱式原型鏈上
          const _instanceof = (target, Fn) => {
            if ((typeof target !== 'object' && typeof target !== 'function') || target === null)
            return false
            
            let proto = target.__proto__
            while (true) {
              if (proto === nullreturn false
              if (proto === Fn.prototype) return true
              proto = proto.__proto__
            }
          }
          function A() {}
          const a = new A()
          console.log(_instanceof(a, A)) // true
          console.log(_instanceof(1, A)) // false
          復(fù)制代碼

          Array.prototype.map

          • map 中的 exc 接受三個(gè)參數(shù),分別是: 元素值、元素下標(biāo)和原數(shù)組
          • map 返回的是一個(gè)新的數(shù)組,地址不一樣
          // 這里不能直接使用箭頭函數(shù),否則無法訪問到 this
          Array.prototype._map = function (exc{
            const result = []
            this.forEach((item, index, arr) => {
              result[index] = exc(item, index, arr)
            })
            return result
          }
          const a = new Array(2).fill(2)
          console.log(a.map((item, index, arr) => item * index + 1)) // [1,3]
          console.log(a._map((item, index, arr) => item * index + 1))// [1,3]
          復(fù)制代碼

          Array.prototype.filter

          • filter 中的 exc 接受三個(gè)參數(shù),與map一致,主要實(shí)現(xiàn)的是數(shù)組的過濾功能,會(huì)根據(jù) exc 函數(shù)的返回值來判斷是否“留下”該值。
          • filter 返回的是一個(gè)新的數(shù)組,地址不一致。
          Array.prototype._filter = function (exc{
            const result = []
            this.forEach((item, index, arr) => {
              if (exc(item, index, arr)) {
                result.push(item)
              }
            })
            return result
          }
          const b = [13456251820]

          console.log(b._filter(item => item % 2 === 0)) // [ 4, 6, 2, 8, 20 ]
          復(fù)制代碼

          Array.prototype.reduce

          • reduce 接受兩個(gè)參數(shù),第一個(gè)為 exc 函數(shù),第二個(gè)為初始值,如果不傳默認(rèn)為 0
          • reduce 最終會(huì)返回一個(gè)值,當(dāng)然不一定是 Number 類型的,取決于你是怎么計(jì)算的,每次的計(jì)算結(jié)果都會(huì)作為下次 exc 中的第一個(gè)參數(shù)
          Array.prototype._reduce = function (exc, initial = 0{
            let result = initial
            this.forEach((item) => {
              result = exc(result, item)
            })
            return result
          }
          console.log(b.reduce((pre, cur) => pre + cur, 0)) // 55
          console.log(b._reduce((pre, cur) => pre + cur, 0)) // 55
          復(fù)制代碼

          Object.create

          MDN[1] Object.create() 方法用于創(chuàng)建一個(gè)新對(duì)象,使用現(xiàn)有的對(duì)象來作為新創(chuàng)建對(duì)象的原型(prototype)。

          Object.prototype._create = function (proto{
            const Fn = function () { }
            Fn.prototype = proto
            return new Fn()
          }
          function A() { }
          const obj = Object.create(A)
          const obj2 = Object._create(A)
          console.log(obj.__proto__ === A) // true
          console.log(obj.__proto__ === A) // true
          復(fù)制代碼

          Function.prototype.call

          call() 方法使用一個(gè)指定的 this 值和單獨(dú)給出的一個(gè)或多個(gè)參數(shù)來調(diào)用一個(gè)函數(shù)。

          Function.prototype._call = function (ctx, ...args{
            // 如果不為空,則需要進(jìn)行對(duì)象包裝
            const o = ctx == undefined ? window : Object(ctx)
            // 給 ctx 添加獨(dú)一無二的屬性
            const key = Symbol()
            o[key] = this
            // 執(zhí)行函數(shù),得到返回結(jié)果
            const result = o[key](...args)
            // 刪除該屬性
            delete o[key]
            return result
          }

          const obj = {
            name'11',
            fun() {
              console.log(this.name)
            }
          }

          const obj2 = { name'22' }
          obj.fun() // 11
          obj.fun.call(obj2) // 22
          obj.fun._call(obj2) // 22
          復(fù)制代碼

          Function.prototype.bind

          bind() 方法創(chuàng)建一個(gè)新的函數(shù),在 bind() 被調(diào)用時(shí),這個(gè)新函數(shù)的 this 被指定為 bind() 的第一個(gè)參數(shù),而其余參數(shù)將作為新函數(shù)的參數(shù),供調(diào)用時(shí)使用。

          const obj = {
            name: '11',
            fun() {
              console.log(this.name)
            }
          }
          Function.prototype._bind = function (ctx, ...args) {
            // 獲取函數(shù)體
            const _self = this
            // 用一個(gè)新函數(shù)包裹,避免立即執(zhí)行
            const bindFn = (...reset) => {
              return _self.call(ctx, ...args, ...reset)
            }
            return bindFn
          }
          const obj2 = { name: '22' }
          obj.fun() // 11
          const fn = obj.fun.bind(obj2)
          const fn2 = obj.fun._bind(obj2)
          fn() // 22
          fn2() // 22
          復(fù)制代碼

          New 關(guān)鍵字

          new 運(yùn)算符創(chuàng)建一個(gè)用戶定義的對(duì)象類型的實(shí)例或具有構(gòu)造函數(shù)的內(nèi)置對(duì)象的實(shí)例。

          const _new = function(constructor{
            // 創(chuàng)建一個(gè)空對(duì)象
            const obj = {}
            // 原型鏈掛載
            obj.__proto__ = constructor.prototype;
            // 將obj 復(fù)制給構(gòu)造體中的 this,并且返回結(jié)果
            const result = constructor.call(obj)
            // 如果返回對(duì)象不為一個(gè)對(duì)象則直接返回剛才創(chuàng)建的對(duì)象
            return typeof result === 'object' && result !== null ? : result : obj
          }
          復(fù)制代碼

          淺拷貝

          const _shallowClone = target => {
            // 基本數(shù)據(jù)類型直接返回
            if (typeof target === 'object' && target !== null) {
              // 獲取target 的構(gòu)造體
              const constructor = target.constructor
              // 如果構(gòu)造體為以下幾種類型直接返回
              if (/^(Function|RegExp|Date|Map|Set)$/i.test(constructor.name)) return target
              // 判斷是否是一個(gè)數(shù)組
              const cloneTarget = Array.isArray(target) ? [] : {}
              for (prop in target) {
                // 只拷貝其自身的屬性
                if (target.hasOwnProperty(prop)) {
                  cloneTarget[prop] = target[prop]
                }
              }
              return cloneTarget
            } else {
              return target
            }
          }
          復(fù)制代碼

          深拷貝

          實(shí)現(xiàn)思路和淺拷貝一致,只不過需要注意幾點(diǎn)

          1. 函數(shù) 正則 日期 ES6新對(duì)象 等不是直接返回其地址,而是重新創(chuàng)建
          2. 需要避免出現(xiàn)循環(huán)引用的情況
          const _completeDeepClone = (target, map = new WeakMap()) => {
            // 基本數(shù)據(jù)類型,直接返回
            if (typeof target !== 'object' || target === nullreturn target
            // 函數(shù) 正則 日期 ES6新對(duì)象,執(zhí)行構(gòu)造題,返回新的對(duì)象
            const constructor = target.constructor
            if (/^(Function|RegExp|Date|Map|Set)$/i.test(constructor.name)) return new constructor(target)
            // map標(biāo)記每一個(gè)出現(xiàn)過的屬性,避免循環(huán)引用
            if (map.get(target)) return map.get(target)
            map.set(target, true)
            const cloneTarget = Array.isArray(target) ? [] : {}
            for (prop in target) {
              if (target.hasOwnProperty(prop)) {
                cloneTarget[prop] = _completeDeepClone(target[prop], map)
              }
            }
            return cloneTarget
          }
          復(fù)制代碼

          寄生組合式繼承

          一圖勝千言

          function Parent(name{
            this.name = name
          }
          Parent.prototype.getName = function () {
            return this.name
          }

          function Son(name, age{
            // 這里其實(shí)就等于 this.name = name
            Parent.call(this, name)
            this.age = age
          }

          Son.prototype.getAge = function () {
            return this.age
          }
          Son.prototype.__proto__ = Object.create(Parent.prototype)

          const son1 = new Son('shao'20)

          console.log(son1.getName()) // shao
          console.log(son1.getAge()) // 20
          復(fù)制代碼

          發(fā)布訂閱者模式

          class EventEmitter {
            constructor() {
              // key: 事件名
              // value: callback [] 回調(diào)數(shù)組
              this.events = {}
            }
            on(name, callback) {
              if (this.events[name]) {
                this.events[name].push(callback)
              } else {
                this.events[name] = [callback]
              }
            }
            off(name, callback) {
              if (!this.message[name]) return;
              if (!callback) {
                // 如果沒有callback,就刪掉整個(gè)事件
                this.message[name] = undefined;
              }
              this.message[name] = this.message[name].filter((item) => item !== callback);

            }
            emit(name, ...args) {
              if (!this.events[name]) return
              this.events[name].forEach(cb => cb(...args))
            }
          }
          復(fù)制代碼

          觀察者模式

          class Observerd {
            constructor() {
              // 我要看看到底有多少人在觀察俺
              this.observerList = []
            }
            addObserver(observer) {
              // 添加一個(gè)觀察俺的人
              this.observerList.push(observer)
            }
            notify() {
              // 我要鬧點(diǎn)動(dòng)靜,所有觀察者都會(huì)知道這個(gè)信息,具體怎么做就是他們自己的事情了
              this.observerList.forEach(observer => observer.update())
            }
          }


          class Observer {
            constructor(doSome) {
              // 觀察到小白鼠有動(dòng)靜之后,觀察者做的事情
              this.doSome = doSome
            }
            update() {
              console.log(this.doSome)
            }
          }

          const ob1 = new Observer('我是ob1,我觀察到小白鼠有反應(yīng)了,太餓了,我得去吃個(gè)飯了')
          const ob2 = new Observer('我是ob2,我觀察到小白鼠有反應(yīng)了,我要繼續(xù)工作!')
          const xiaoBaiShu = new Observerd()
          xiaoBaiShu.addObserver(ob1)
          xiaoBaiShu.addObserver(ob2)
          xiaoBaiShu.notify() // .... .... 
          復(fù)制代碼

          多說一句:怎么理解發(fā)布訂閱者和觀察者的區(qū)別呢 ?

          其實(shí)發(fā)布訂閱者模式只有一個(gè)中間者,好像啥事情都需要它親自來做。而且仔細(xì)觀察的話,發(fā)布訂閱者模式會(huì)存在一個(gè)事件名和事件的對(duì)應(yīng)關(guān)系,今天可以發(fā)布天氣預(yù)報(bào),只有訂閱了天氣預(yù)報(bào)的才會(huì)被通知,訂閱了 KFC瘋狂星期四鬧鐘事件 的不會(huì)被提醒。

          而觀察者模式,等被觀察者發(fā)出了一點(diǎn)動(dòng)靜(執(zhí)行notify),所有觀察者都會(huì)被通知。

          節(jié)流

          節(jié)流函數(shù)(throttle)就是讓事件處理函數(shù)(handler)在大于等于執(zhí)行周期時(shí)才能執(zhí)行,周期之內(nèi)不執(zhí)行,即事件一直被觸發(fā),那么事件將會(huì)按每小段固定時(shí)間一次的頻率執(zhí)行。

          function throttle(fn, delay = 300{
            // 這里始終記得字節(jié)二面的時(shí)候,建議我不寫 flag 好家伙
            let isThrottling = false
            // 核心思路,函數(shù)多次執(zhí)行只有當(dāng) isThrottling 為 false 時(shí)才會(huì)進(jìn)入函數(shù)體
            return function (...args{
              if (!isThrottling) {
                isThrottling = true
                setTimeout(() => {
                  isThrottling = false
                  fn.apply(this, args)
                }, delay)
              }
            }
          }
          復(fù)制代碼

          防抖

          事件響應(yīng)函數(shù)在一段時(shí)間后才執(zhí)行,如果這段時(shí)間內(nèi)再次調(diào)用,則重新計(jì)算執(zhí)行時(shí)間

          function debounce(fn, delay = 300{
            let timer = null
            return function (...args{
              // 每次進(jìn)來都會(huì)清空定時(shí)器,所以在 delay 事件中重復(fù)執(zhí)行之后執(zhí)行最后一次
              clearInterval(timer)
              timer = setTimeout(() => {
                fn.apply(this, args)
              }, delay)
            }
          }
          復(fù)制代碼

          once 函數(shù)

          函數(shù)返回結(jié)果會(huì)被緩存下來,只會(huì)計(jì)算一次。

          const f = (x) => x;
          const onceF = once(f);
          //=> 3
          onceF(3);
          //=> 3
          onceF(4);
          復(fù)制代碼
          const once = (fn) => {
            let res, isFirst = true
            return function (...args{
              if (!isFirst) return res
              res = fn.call(this, ...args)
              isFirst = false
              return res
            }
          }
          復(fù)制代碼

          累加函數(shù)應(yīng)用

          實(shí)現(xiàn)一個(gè)累加函數(shù),下面的幾種情況都能正確的調(diào)用。

          console.log(sum(1, 2)(3)()) // 6
          console.log(sum(1)(2)(3)()) // 6
          console.log(sum(1, 2, 4)(4)()) // 11
          復(fù)制代碼
          function sum(...args) {
            let params = args
            const _sum = (...newArgs) => {
              if (newArgs.length === 0) {
                return params.reduce((pre, cur) => pre + cur, 0)
              } else {
                params = [...params, ...newArgs]
                return _sum
              }
            }
            return _sum
          }
          復(fù)制代碼

          進(jìn)階

          實(shí)現(xiàn) repeat 方法

          function repeat(fn, times, delay{
            return async function (...args{
              for (let i = 0; i < times; i++) {
                await new Promise((resolve, reject) => {
                  setTimeout(() => {
                    fn.call(this, ...args)
                    resolve()
                  }, delay)
                })
              }
            }
          }
          const repeatFn = repeat(console.log, 41000)
          // 函數(shù)調(diào)用四次,每次間隔 1s 打印 hello
          repeatFn('hello')
          復(fù)制代碼

          實(shí)現(xiàn) Promise.all/race/allSettled/any

          • Promise 身上的這些方法返回的都是一個(gè) Promise
          • Promise.resolve 接受一個(gè) Promise,若非 promise 則將其變成功狀態(tài)的 Promise
          // 有一個(gè)失敗則返回失敗的結(jié)果,全部成功返回全成功的數(shù)組
          Promise.all = function (promiseList = []) {
          return new Promise((resolve, reject) => {
          const result = []
          let count = 0
          if (promiseList.length === 0) {
          resolve(result)
          return
          }
          for (let i = 0; i < promiseList.length; i++) {
          Promise.resolve(promiseList[i]).then(res => {
          result[i] = res
          count++
          // 不能直接通過 result.length 進(jìn)行比較,因?yàn)?會(huì)存在下標(biāo)大的先賦值
          // 例如 i = 3 第一個(gè)返回結(jié)果,此時(shí)數(shù)組變?yōu)閇empty,empty,empty,res]
          if (count === promiseList.length) {
          resolve(result)
          }
          }).catch(e => {
          reject(e)
          })
          }
          })
          }
          // 返回第一個(gè)成功或失敗的結(jié)果
          Promise.race = function (promiseList = []) {
          return new Promise((resolve, reject) => {
          if (promiseList.length === 0) {
          return resolve([])
          }
          for (let i = 0; i < promiseList.length; i++) {
          Promise.resolve(promiseList[i]).then(res => {
          resolve(res)
          }).catch(e => {
          reject(e)
          })
          }
          })
          }
          // 無論成功約否都返回,但是會(huì)添加一個(gè) status 字段用于標(biāo)記成功/失敗
          Promise.allSettled = function (promiseList = []) {
          return new Promise((resolve, reject) => {
          const result = []
          let count = 0

          const addRes = (i, data) => {
          result[i] = data
          count++
          if (count === promiseList.length) {
          resolve(result)
          }
          }

          if (promiseList.length === 0) return resolve(result)
          for (let i = 0; i < promiseList.length; i++) {
          Promise.resolve(promiseList[i]).then(res => {
          addRes(i, { status: 'fulfilled', data: res })
          }).catch(e => {
          addRes(i, { status: 'rejected', data: e })
          })
          }
          })
          }
          // AggregateError,當(dāng)多個(gè)錯(cuò)誤需要包裝在一個(gè)錯(cuò)誤中時(shí),該對(duì)象表示一個(gè)錯(cuò)誤。
          // 和 Promise.all 相反,全部失敗返回失敗的結(jié)果數(shù)組,有一個(gè)成功則返回成功結(jié)果
          Promise.any = function (promiseList = []) {
          return new Promise((resolve, reject) => {
          if (promiseList.length === 0) return resolve([])
          let count = 0
          const result = []
          for (let i = 0; i < promiseList.length; i++) {
          Promise.resolve(promiseList[i]).then(res => {
          resolve(res)
          }).catch(e => {
          count++
          result[i] = e
          if (count === promiseList.length) {
          reject(new AggregateError(result))
          }
          })
          }
          })
          }
          復(fù)制代碼

          整數(shù)千分位加逗號(hào)

          1234567 -> 1,234,567
          復(fù)制代碼
          function toThousands(num) {
            num = num.toString()
            let result = ''
            while (num.length > 3) {
              result = ',' + num.substring(num.length - 3) + result
              num = num.substring(0, num.length - 3)
            }
            result = num + result
            return result
          }
          console.log(toThousands(1234567)) // 1,234,567
          console.log(toThousands(123456)) // 123,456
          復(fù)制代碼

          洗牌函數(shù)

          有幾張牌張牌,用 js 來進(jìn)行亂序排列,要保持公平性

          const shuffle = (arr) => {
          // 不影響原來的數(shù)組
          const result = [...arr]
          for (let i = result.length; i > 0; i--) {
          // 隨機(jī)從 [0,i - 1] 產(chǎn)生一個(gè) index, 將 i - 1 于 index 對(duì)應(yīng)數(shù)組的值進(jìn)行交換
          const index = Math.floor(Math.random() * i);
          [result[index], result[i - 1]] = [result[i - 1], result[index]]
          }
          return result
          }
          const arr = [1, 2, 3, 4, 5]
          console.log(shuffle(arr)) // [ 3, 1, 2, 5, 4 ]
          console.log(shuffle(arr)) // [ 2, 3, 5, 1, 4 ]
          console.log(shuffle(arr)) // [ 4, 2, 3, 1, 5 ]
          console.log(shuffle(arr)) // [ 5, 4, 2, 3, 1 ]
          復(fù)制代碼

          手寫LRU

          LRU是Least Recently Used的縮寫,即最近最少使用,是一種常用的頁面置換算法[2],選擇最近最久未使用的頁面予以淘汰。該算法賦予每個(gè)頁面[3]一個(gè)訪問字段,用來記錄一個(gè)頁面自上次被訪問以來所經(jīng)歷的時(shí)間 t,當(dāng)須淘汰一個(gè)頁面時(shí),選擇現(xiàn)有頁面中其 t 值最大的,即最近最少使用的頁面予以淘汰。

          力扣地址[4]

          /**
           * @param {number} capacity
           */

          var LRUCache = function(capacity) {
              this.map = new Map()
              this.capacity = capacity
          };

          /** 
           * @param {number} key
           * @return {number}
           */

          LRUCache.prototype.get = function(key) {
              if(this.map.has(key)){
                  const value = this.map.get(key)
                  // 更新存儲(chǔ)位置
                  this.map.delete(key)
                  this.map.set(key,value)
                  return value
              }
              return - 1
          };

          /** 
           * @param {number} key 
           * @param {number} value
           * @return {void}
           */

          LRUCache.prototype.put = function(key, value) {
              if(this.map.has(key)){
                  this.map.delete(key)
              }
              this.map.set(key,value)
              // 如果此時(shí)超過了最長(zhǎng)可存儲(chǔ)范圍
              if(this.map.size > this.capacity){
                  // 刪除 map 中最久未使用的元素
                  this.map.delete(this.map.keys().next().value)
              }
          };
          復(fù)制代碼

          更上一層樓

          Generator

          先看看下面輸出的內(nèi)容

          async function getResult() {
              await new Promise((resolve, reject) => {
                  setTimeout(() => {
                      resolve(1);
                      console.log(1);
                  }, 1000);
              })
              await new Promise((resolve, reject) => {
                  setTimeout(() => {
                      resolve(2);
                      console.log(2);
                  }, 500);
              })
              await new Promise((resolve, reject) => {
                  setTimeout(() => {
                      resolve(3);
                      console.log(3);
                  }, 100);
              })

          }
          getResult()
          // 1 2 3 
          復(fù)制代碼

          那如何使用 Es6 中的 generator 實(shí)現(xiàn)類似的效果呢 ?

          functiongetResult(params{
              yield new Promise((resolve, reject) => {
                  setTimeout(() => {
                      resolve(1);
                      console.log(1);
                  }, 1000);
              })
              yield new Promise((resolve, reject) => {
                  setTimeout(() => {
                      resolve(2);
                      console.log(2);
                  }, 500);
              })
              yield new Promise((resolve, reject) => {
                  setTimeout(() => {
                      resolve(3);
                      console.log(3);
                  }, 100);
              })
          }
          const gen = getResult()
          // gen.next().value 就是每一次 yield 之后返回的 Promise
          // gen.next() = {value: yeild 返回的數(shù)據(jù),done: 迭代器是否走完}
          gen.next().value.then(() => {
              gen.next().value.then(() => {
                  gen.next();
              });
          });// 依次打印 1 2 3
          復(fù)制代碼

          將 gen.next() 封裝一層,讓其自己能夠?qū)崿F(xiàn)遞歸調(diào)用

          const gen = getResult()
          function co(g) {
          const nextObj = g.next();
          // 遞歸停止條件:當(dāng)?shù)鞯阶詈笠粋€(gè) yeild
          if (nextObj.done) {
          return;
          }
          nextObj.value.then(()=>{
          co(g)
          })
          }
          co(gen)
          復(fù)制代碼

          async-pool

          JS 控制并發(fā)請(qǐng)求, 參考文章 mp.weixin.qq.com/s/yWOPoef9i…[5]

          aysnc-pool 的基本使用

          const timeout = i => new Promise(resolve => setTimeout(() => resolve(i), i));
          await asyncPool(2, [1000500030002000], timeout);
          復(fù)制代碼

          asyncPool 這個(gè)函數(shù)接受三個(gè)參數(shù)

          • poolLimit(數(shù)字類型):表示限制的并發(fā)數(shù);
          • array(數(shù)組類型):表示任務(wù)數(shù)組;
          • iteratorFn(函數(shù)類型):表示迭代函數(shù),用于實(shí)現(xiàn)對(duì)每個(gè)任務(wù)項(xiàng)進(jìn)行處理,該函數(shù)會(huì)返回一個(gè) Promise 對(duì)象或異步函數(shù)。

          這里提醒一下,promise.then 中的函數(shù)執(zhí)行是一異步的,而賦值是同步的

          const a = Promise.resolve().then(()=>console.log(a))
          // 等價(jià)于 此時(shí) a 等于一個(gè) pending 狀態(tài)的 promise
          const a = Promise.resolve().then()
          a.then(()=>{
            console.log(a)
          })
          復(fù)制代碼

          手寫實(shí)現(xiàn),這部分可能會(huì)多花點(diǎn)時(shí)間。可以拷貝代碼多調(diào)試幾次就知道了

          async function asyncPool(poolLimit, array, iteratorFn) {
          const ret = []; // 存儲(chǔ)所有的異步任務(wù)
          const executing = []; // 存儲(chǔ)正在執(zhí)行的異步任務(wù)
          for (const item of array) {
          // 調(diào)用iteratorFn函數(shù)創(chuàng)建異步任務(wù)
          const p = Promise.resolve().then(() => iteratorFn(item, array));
          ret.push(p); // 保存新的異步任務(wù)

          // 當(dāng)poolLimit值小于或等于總?cè)蝿?wù)個(gè)數(shù)時(shí),進(jìn)行并發(fā)控制
          if (poolLimit <= array.length) {
          // 當(dāng)任務(wù)完成后,從正在執(zhí)行的任務(wù)數(shù)組中移除已完成的任務(wù)
          const e = p.then(() => executing.splice(executing.indexOf(e), 1));
          executing.push(e); // 保存正在執(zhí)行的異步任務(wù)
          if (executing.length >= poolLimit) {
          await Promise.race(executing); // 等待較快的任務(wù)執(zhí)行完成
          }
          }
          }
          return Promise.all(ret);
          }

          const timeout = i => new Promise(resolve => setTimeout(() => { console.log(i); resolve(i) }, i));
          // 當(dāng)然,limit <= 0 的時(shí)候 我們可以理解為只允許一個(gè)請(qǐng)求存在
          asyncPool(2, [1000, 5000, 3000, 2000], timeout).then(res => {
          console.log(res)
          })
          復(fù)制代碼

          總共花費(fèi) 6 s 時(shí)間,符合預(yù)期

          總結(jié)

          收集這些手寫的時(shí)候,自己也學(xué)到了很多東西,其實(shí)也是一個(gè)查漏補(bǔ)缺的過程。感謝你看到這里,點(diǎn)贊收藏 offer ++。


          關(guān)于本文

          作者:愛喝雀巢的邵小白

          https://juejin.cn/post/7137961562794852383

          Node 社群



          我組建了一個(gè)氛圍特別好的 Node.js 社群,里面有很多 Node.js小伙伴,如果你對(duì)Node.js學(xué)習(xí)感興趣的話(后續(xù)有計(jì)劃也可以),我們可以一起進(jìn)行Node.js相關(guān)的交流、學(xué)習(xí)、共建。下方加 考拉 好友回復(fù)「Node」即可。



          如果你覺得這篇內(nèi)容對(duì)你有幫助,我想請(qǐng)你幫我2個(gè)小忙:

          1. 點(diǎn)個(gè)「在看」,讓更多人也能看到這篇文章
          2. 訂閱官方博客 www.inode.club 讓我們一起成長(zhǎng)

          點(diǎn)贊和在看就是最大的支持??

          瀏覽 38
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  久色网站 | 欧美草逼大全 | 激情五月综合网 | 香蕉久久地址一 | 天天操天天添 |