<p id="m2nkj"><option id="m2nkj"><big id="m2nkj"></big></option></p>
    <strong id="m2nkj"></strong>
    <ruby id="m2nkj"></ruby>

    <var id="m2nkj"></var>
  • 死磕 36 個(gè) JS 手寫(xiě)題(搞懂后,提升真的大)

    共 80474字,需瀏覽 161分鐘

     ·

    2021-04-21 16:13

    這是布蘭的第 22 篇原創(chuàng)

    為什么要寫(xiě)這類(lèi)文章

    作為一個(gè)程序員,代碼能力毋庸置疑是非常非常重要的,就像現(xiàn)在為什么大廠(chǎng)面試基本都問(wèn)什么 API 怎么實(shí)現(xiàn)可見(jiàn)其重要性。我想說(shuō)的是居然手寫(xiě)這么重要,那我們就必須掌握它,所以文章標(biāo)題用了死磕,一點(diǎn)也不過(guò)分,也希望不被認(rèn)為是標(biāo)題黨。

    作為一個(gè)普通前端,我是真的寫(xiě)不出 Promise A+ 規(guī)范,但是沒(méi)關(guān)系,我們可以站在巨人的肩膀上,要相信我們現(xiàn)在要走的路,前人都走過(guò),所以可以找找現(xiàn)在社區(qū)已經(jīng)存在的那些優(yōu)秀的文章,比如工業(yè)聚大佬寫(xiě)的 100 行代碼實(shí)現(xiàn) Promises/A+ 規(guī)范,找到這些文章后不是收藏夾吃灰,得找個(gè)時(shí)間踏踏實(shí)實(shí)的學(xué),一行一行的磨,直到搞懂為止。我現(xiàn)在就是這么干的。

    能收獲什么

    這篇文章總體上分為 2 類(lèi)手寫(xiě)題,前半部分可以歸納為是常見(jiàn)需求,后半部分則是對(duì)現(xiàn)有技術(shù)的實(shí)現(xiàn);

    • 對(duì)常用的需求進(jìn)行手寫(xiě)實(shí)現(xiàn),比如數(shù)據(jù)類(lèi)型判斷函數(shù)、深拷貝等可以直接用于往后的項(xiàng)目中,提高了項(xiàng)目開(kāi)發(fā)效率;
    • 對(duì)現(xiàn)有關(guān)鍵字和 API 的實(shí)現(xiàn),可能需要用到別的知識(shí)或 API,比如在寫(xiě) forEach 的時(shí)候用到了無(wú)符號(hào)位右移的操作,平時(shí)都不怎么能夠接觸到這玩意,現(xiàn)在遇到了就可以順手把它掌握了。所以手寫(xiě)這些實(shí)現(xiàn)能夠潛移默化的擴(kuò)展并鞏固自己的 JS 基礎(chǔ);
    • 通過(guò)寫(xiě)各種測(cè)試用例,你會(huì)知道各種 API 的邊界情況,比如 Promise.all, 你得考慮到傳入?yún)?shù)的各種情況,從而加深了對(duì)它們的理解及使用;

    閱讀的時(shí)候需要做什么

    閱讀的時(shí)候,你需要把每行代碼都看懂,知道它在干什么,為什么要這么寫(xiě),能寫(xiě)得更好嘛?比如在寫(xiě)圖片懶加載的時(shí)候,一般我們都是根據(jù)當(dāng)前元素的位置和視口進(jìn)行判斷是否要加載這張圖片,普通程序員寫(xiě)到這就差不多完成了。而大佬程序員則是會(huì)多考慮一些細(xì)節(jié)的東西,比如性能如何更優(yōu)?代碼如何更精簡(jiǎn)?比如 yeyan1996 寫(xiě)的圖片懶加載就多考慮了 2 點(diǎn):比如圖片全部加載完成的時(shí)候得把事件監(jiān)聽(tīng)給移除;比如加載完一張圖片的時(shí)候,得把當(dāng)前 img 從 imgList 里移除,起到優(yōu)化內(nèi)存的作用。

    除了讀通代碼之外,還可以打開(kāi) Chrome 的 Script snippet 去寫(xiě)測(cè)試用例跑跑代碼,做到更好的理解以及使用。

    在看了幾篇以及寫(xiě)了很多測(cè)試用例的前提下,嘗試自己手寫(xiě)實(shí)現(xiàn),看看自己到底掌握了多少。條條大路通羅馬,你還能有別的方式實(shí)現(xiàn)嘛?或者你能寫(xiě)得比別人更好嘛?

    好了,還楞著干啥,開(kāi)始干活。

    數(shù)據(jù)類(lèi)型判斷

    typeof 可以正確識(shí)別:Undefined、Boolean、Number、String、Symbol、Function 等類(lèi)型的數(shù)據(jù),但是對(duì)于其他的都會(huì)認(rèn)為是 object,比如 Null、Date 等,所以通過(guò) typeof 來(lái)判斷數(shù)據(jù)類(lèi)型會(huì)不準(zhǔn)確。但是可以使用 Object.prototype.toString 實(shí)現(xiàn)。

    function typeOf(obj{
        let res = Object.prototype.toString.call(obj).split(' ')[1]
        res = res.substring(0, res.length - 1).toLowerCase()
        return res
    }
    typeOf([])        // 'array'
    typeOf({})        // 'object'
    typeOf(new Date)  // 'date'

    繼承

    原型鏈繼承

    function Animal({
        this.colors = ['black''white']
    }
    Animal.prototype.getColor = function({
        return this.colors
    }
    function Dog({}
    Dog.prototype =  new Animal()

    let dog1 = new Dog()
    dog1.colors.push('brown')
    let dog2 = new Dog()
    console.log(dog2.colors)  // ['black', 'white', 'brown']

    原型鏈繼承存在的問(wèn)題:

    • 問(wèn)題1:原型中包含的引用類(lèi)型屬性將被所有實(shí)例共享;
    • 問(wèn)題2:子類(lèi)在實(shí)例化的時(shí)候不能給父類(lèi)構(gòu)造函數(shù)傳參;

    借用構(gòu)造函數(shù)實(shí)現(xiàn)繼承

    function Animal(name{
        this.name = name
        this.getName = function({
            return this.name
        }
    }
    function Dog(name{
        Animal.call(this, name)
    }
    Dog.prototype =  new Animal()

    借用構(gòu)造函數(shù)實(shí)現(xiàn)繼承解決了原型鏈繼承的 2 個(gè)問(wèn)題:引用類(lèi)型共享問(wèn)題以及傳參問(wèn)題。但是由于方法必須定義在構(gòu)造函數(shù)中,所以會(huì)導(dǎo)致每次創(chuàng)建子類(lèi)實(shí)例都會(huì)創(chuàng)建一遍方法。

    組合繼承

    組合繼承結(jié)合了原型鏈和盜用構(gòu)造函數(shù),將兩者的優(yōu)點(diǎn)集中了起來(lái)。基本的思路是使用原型鏈繼承原型上的屬性和方法,而通過(guò)盜用構(gòu)造函數(shù)繼承實(shí)例屬性。這樣既可以把方法定義在原型上以實(shí)現(xiàn)重用,又可以讓每個(gè)實(shí)例都有自己的屬性。

    function Animal(name{
        this.name = name
        this.colors = ['black''white']
    }
    Animal.prototype.getName = function({
        return this.name
    }
    function Dog(name, age{
        Animal.call(this, name)
        this.age = age
    }
    Dog.prototype =  new Animal()
    Dog.prototype.constructor = Dog

    let dog1 = new Dog('奶昔'2)
    dog1.colors.push('brown')
    let dog2 = new Dog('哈赤'1)
    console.log(dog2) 
    // { name: "哈赤", colors: ["black", "white"], age: 1 }

    寄生式組合繼承

    組合繼承已經(jīng)相對(duì)完善了,但還是存在問(wèn)題,它的問(wèn)題就是調(diào)用了 2 次父類(lèi)構(gòu)造函數(shù),第一次是在 new Animal(),第二次是在 Animal.call() 這里。

    所以解決方案就是不直接調(diào)用父類(lèi)構(gòu)造函數(shù)給子類(lèi)原型賦值,而是通過(guò)創(chuàng)建空函數(shù) F 獲取父類(lèi)原型的副本。

    寄生式組合繼承寫(xiě)法上和組合繼承基本類(lèi)似,區(qū)別是如下這里:

    - Dog.prototype =  new Animal()
    - Dog.prototype.constructor = Dog

    + function F() {}
    + F.prototype = Animal.prototype
    + let f = new F()
    + f.constructor = Dog
    + Dog.prototype = f

    稍微封裝下上面添加的代碼后:

    function object(o{
        function F({}
        F.prototype = o
        return new F()
    }
    function inheritPrototype(child, parent{
        let prototype = object(parent.prototype)
        prototype.constructor = child
        child.prototype = prototype
    }
    inheritPrototype(Dog, Animal)

    如果你嫌棄上面的代碼太多了,還可以基于組合繼承的代碼改成最簡(jiǎn)單的寄生式組合繼承:

    - Dog.prototype =  new Animal()
    - Dog.prototype.constructor = Dog

    + Dog.prototype =  Object.create(Animal.prototype)
    + Dog.prototype.constructor = Dog

    class 實(shí)現(xiàn)繼承

    class Animal {
        constructor(name) {
            this.name = name
        } 
        getName() {
            return this.name
        }
    }
    class Dog extends Animal {
        constructor(name, age) {
            super(name)
            this.age = age
        }
    }

    數(shù)組去重

    ES5 實(shí)現(xiàn):

    function unique(arr{
        var res = arr.filter(function(item, index, array{
            return array.indexOf(item) === index
        })
        return res
    }

    ES6 實(shí)現(xiàn):

    var unique = arr => [...new Set(arr)]

    數(shù)組扁平化

    數(shù)組扁平化就是將 [1, [2, [3]]] 這種多層的數(shù)組拍平成一層 [1, 2, 3]。使用 Array.prototype.flat 可以直接將多層數(shù)組拍平成一層:

    [1, [2, [3]]].flat(2)  // [1, 2, 3]

    現(xiàn)在就是要實(shí)現(xiàn) flat 這種效果。

    ES5 實(shí)現(xiàn):遞歸。

    function flatten(arr{
        var result = [];
        for (var i = 0, len = arr.length; i < len; i++) {
            if (Array.isArray(arr[i])) {
                result = result.concat(flatten(arr[i]))
            } else {
                result.push(arr[i])
            }
        }
        return result;
    }

    ES6 實(shí)現(xiàn):

    function flatten(arr{
        while (arr.some(item => Array.isArray(item))) {
            arr = [].concat(...arr);
        }
        return arr;
    }

    深淺拷貝

    淺拷貝:只考慮對(duì)象類(lèi)型。

    function shallowCopy(obj{
        if (typeof obj !== 'object'return
        
        let newObj = obj instanceof Array ? [] : {}
        for (let key in obj) {
            if (obj.hasOwnProperty(key)) {
                newObj[key] = obj[key]
            }
        }
        return newObj
    }

    簡(jiǎn)單版深拷貝:只考慮普通對(duì)象屬性,不考慮內(nèi)置對(duì)象和函數(shù)。

    function deepClone(obj{
        if (typeof obj !== 'object'return;
        var newObj = obj instanceof Array ? [] : {};
        for (var key in obj) {
            if (obj.hasOwnProperty(key)) {
                newObj[key] = typeof obj[key] === 'object' ? deepClone(obj[key]) : obj[key];
            }
        }
        return newObj;
    }

    復(fù)雜版深克?。夯诤?jiǎn)單版的基礎(chǔ)上,還考慮了內(nèi)置對(duì)象比如 Date、RegExp 等對(duì)象和函數(shù)以及解決了循環(huán)引用的問(wèn)題。

    const isObject = (target) => (typeof target === "object" || typeof target === "function") && target !== null;

    function deepClone(target, map = new WeakMap()) {
        if (map.get(target)) {
            return target;
        }
        // 獲取當(dāng)前值的構(gòu)造函數(shù):獲取它的類(lèi)型
        let constructor = target.constructor;
        // 檢測(cè)當(dāng)前對(duì)象target是否與正則、日期格式對(duì)象匹配
        if (/^(RegExp|Date)$/i.test(constructor.name)) {
            // 創(chuàng)建一個(gè)新的特殊對(duì)象(正則類(lèi)/日期類(lèi))的實(shí)例
            return new constructor(target);  
        }
        if (isObject(target)) {
            map.set(target, true);  // 為循環(huán)引用的對(duì)象做標(biāo)記
            const cloneTarget = Array.isArray(target) ? [] : {};
            for (let prop in target) {
                if (target.hasOwnProperty(prop)) {
                    cloneTarget[prop] = deepClone(target[prop], map);
                }
            }
            return cloneTarget;
        } else {
            return target;
        }
    }

    事件總線(xiàn)(發(fā)布訂閱模式)

    class EventEmitter {
        constructor() {
            this.cache = {}
        }
        on(name, fn) {
            if (this.cache[name]) {
                this.cache[name].push(fn)
            } else {
                this.cache[name] = [fn]
            }
        }
        off(name, fn) {
            let tasks = this.cache[name]
            if (tasks) {
                const index = tasks.findIndex(f => f === fn || f.callback === fn)
                if (index >= 0) {
                    tasks.splice(index, 1)
                }
            }
        }
        emit(name, once = false, ...args) {
            if (this.cache[name]) {
                // 創(chuàng)建副本,如果回調(diào)函數(shù)內(nèi)繼續(xù)注冊(cè)相同事件,會(huì)造成死循環(huán)
                let tasks = this.cache[name].slice()
                for (let fn of tasks) {
                    fn(...args)
                }
                if (once) {
                    delete this.cache[name]
                }
            }
        }
    }

    // 測(cè)試
    let eventBus = new EventEmitter()
    let fn1 = function(name, age{
        console.log(`${name} ${age}`)
    }
    let fn2 = function(name, age{
        console.log(`hello, ${name} ${age}`)
    }
    eventBus.on('aaa', fn1)
    eventBus.on('aaa', fn2)
    eventBus.emit('aaa'false'布蘭'12)
    // '布蘭 12'
    // 'hello, 布蘭 12'

    解析 URL 參數(shù)為對(duì)象

    function parseParam(url{
        const paramsStr = /.+\?(.+)$/.exec(url)[1]; // 將 ? 后面的字符串取出來(lái)
        const paramsArr = paramsStr.split('&'); // 將字符串以 & 分割后存到數(shù)組中
        let paramsObj = {};
        // 將 params 存到對(duì)象中
        paramsArr.forEach(param => {
            if (/=/.test(param)) { // 處理有 value 的參數(shù)
                let [key, val] = param.split('='); // 分割 key 和 value
                val = decodeURIComponent(val); // 解碼
                val = /^\d+$/.test(val) ? parseFloat(val) : val; // 判斷是否轉(zhuǎn)為數(shù)字
        
                if (paramsObj.hasOwnProperty(key)) { // 如果對(duì)象有 key,則添加一個(gè)值
                    paramsObj[key] = [].concat(paramsObj[key], val);
                } else { // 如果對(duì)象沒(méi)有這個(gè) key,創(chuàng)建 key 并設(shè)置值
                    paramsObj[key] = val;
                }
            } else { // 處理沒(méi)有 value 的參數(shù)
                paramsObj[param] = true;
            }
        })
        
        return paramsObj;
    }

    字符串模板

    function render(template, data{
        const reg = /\{\{(\w+)\}\}/// 模板字符串正則
        if (reg.test(template)) { // 判斷模板里是否有模板字符串
            const name = reg.exec(template)[1]; // 查找當(dāng)前模板里第一個(gè)模板字符串的字段
            template = template.replace(reg, data[name]); // 將第一個(gè)模板字符串渲染
            return render(template, data); // 遞歸的渲染并返回渲染后的結(jié)構(gòu)
        }
        return template; // 如果模板沒(méi)有模板字符串直接返回
    }

    測(cè)試:

    let template = '我是{{name}},年齡{{age}},性別{{sex}}';
    let person = {
        name'布蘭',
        age12
    }
    render(template, person); // 我是布蘭,年齡12,性別undefined

    圖片懶加載

    與普通的圖片懶加載不同,如下這個(gè)多做了 2 個(gè)精心處理:

    • 圖片全部加載完成后移除事件監(jiān)聽(tīng);
    • 加載完的圖片,從 imgList 移除;
    let imgList = [...document.querySelectorAll('img')]
    let length = imgList.length

    const imgLazyLoad = function({
        let count = 0
        return (function({
            let deleteIndexList = []
            imgList.forEach((img, index) => {
                let rect = img.getBoundingClientRect()
                if (rect.top < window.innerHeight) {
                    img.src = img.dataset.src
                    deleteIndexList.push(index)
                    count++
                    if (count === length) {
                        document.removeEventListener('scroll', imgLazyLoad)
                    }
                }
            })
            imgList = imgList.filter((img, index) => !deleteIndexList.includes(index))
        })()
    }

    // 這里最好加上防抖處理
    document.addEventListener('scroll', imgLazyLoad)

    參考:圖片懶加載[1]

    函數(shù)防抖

    觸發(fā)高頻事件 N 秒后只會(huì)執(zhí)行一次,如果 N 秒內(nèi)事件再次觸發(fā),則會(huì)重新計(jì)時(shí)。

    簡(jiǎn)單版:函數(shù)內(nèi)部支持使用 this 和 event 對(duì)象;

    function debounce(func, wait{
        var timeout;
        return function ({
            var context = this;
            var args = arguments;
            clearTimeout(timeout)
            timeout = setTimeout(function(){
                func.apply(context, args)
            }, wait);
        }
    }

    使用:

    var node = document.getElementById('layout')
    function getUserAction(e{
        console.log(this, e)  // 分別打?。簄ode 這個(gè)節(jié)點(diǎn) 和 MouseEvent
        node.innerHTML = count++;
    };
    node.onmousemove = debounce(getUserAction, 1000)

    最終版:除了支持 this 和 event 外,還支持以下功能:

    • 支持立即執(zhí)行;
    • 函數(shù)可能有返回值;
    • 支持取消功能;
    function debounce(func, wait, immediate{
        var timeout, result;
        
        var debounced = function ({
            var context = this;
            var args = arguments;
            
            if (timeout) clearTimeout(timeout);
            if (immediate) {
                // 如果已經(jīng)執(zhí)行過(guò),不再執(zhí)行
                var callNow = !timeout;
                timeout = setTimeout(function(){
                    timeout = null;
                }, wait)
                if (callNow) result = func.apply(context, args)
            } else {
                timeout = setTimeout(function(){
                    func.apply(context, args)
                }, wait);
            }
            return result;
        };

        debounced.cancel = function({
            clearTimeout(timeout);
            timeout = null;
        };

        return debounced;
    }

    使用:

    var setUseAction = debounce(getUserAction, 10000true);
    // 使用防抖
    node.onmousemove = setUseAction

    // 取消防抖
    setUseAction.cancel()

    參考:JavaScript專(zhuān)題之跟著underscore學(xué)防抖

    函數(shù)節(jié)流

    觸發(fā)高頻事件,且 N 秒內(nèi)只執(zhí)行一次。

    簡(jiǎn)單版:使用時(shí)間戳來(lái)實(shí)現(xiàn),立即執(zhí)行一次,然后每 N 秒執(zhí)行一次。

    function throttle(func, wait{
        var context, args;
        var previous = 0;

        return function({
            var now = +new Date();
            context = this;
            args = arguments;
            if (now - previous > wait) {
                func.apply(context, args);
                previous = now;
            }
        }
    }

    最終版:支持取消節(jié)流;另外通過(guò)傳入第三個(gè)參數(shù),options.leading 來(lái)表示是否可以立即執(zhí)行一次,opitons.trailing 表示結(jié)束調(diào)用的時(shí)候是否還要執(zhí)行一次,默認(rèn)都是 true。注意設(shè)置的時(shí)候不能同時(shí)將 leading 或 trailing 設(shè)置為 false。

    function throttle(func, wait, options{
        var timeout, context, args, result;
        var previous = 0;
        if (!options) options = {};

        var later = function({
            previous = options.leading === false ? 0 : new Date().getTime();
            timeout = null;
            func.apply(context, args);
            if (!timeout) context = args = null;
        };

        var throttled = function({
            var now = new Date().getTime();
            if (!previous && options.leading === false) previous = now;
            var remaining = wait - (now - previous);
            context = this;
            args = arguments;
            if (remaining <= 0 || remaining > wait) {
                if (timeout) {
                    clearTimeout(timeout);
                    timeout = null;
                }
                previous = now;
                func.apply(context, args);
                if (!timeout) context = args = null;
            } else if (!timeout && options.trailing !== false) {
                timeout = setTimeout(later, remaining);
            }
        };
        
        throttled.cancel = function({
            clearTimeout(timeout);
            previous = 0;
            timeout = null;
        }
        return throttled;
    }

    節(jié)流的使用就不拿代碼舉例了,參考防抖的寫(xiě)就行。

    參考:JavaScript專(zhuān)題之跟著 underscore 學(xué)節(jié)流

    函數(shù)柯里化

    什么叫函數(shù)柯里化?其實(shí)就是將使用多個(gè)參數(shù)的函數(shù)轉(zhuǎn)換成一系列使用一個(gè)參數(shù)的函數(shù)的技術(shù)。還不懂?來(lái)舉個(gè)例子。

    function add(a, b, c{
        return a + b + c
    }
    add(123)
    let addCurry = curry(add)
    addCurry(1)(2)(3)

    現(xiàn)在就是要實(shí)現(xiàn) curry 這個(gè)函數(shù),使函數(shù)從一次調(diào)用傳入多個(gè)參數(shù)變成多次調(diào)用每次傳一個(gè)參數(shù)。

    function curry(fn{
        let judge = (...args) => {
            if (args.length == fn.length) return fn(...args)
            return (...arg) => judge(...args, ...arg)
        }
        return judge
    }

    偏函數(shù)

    什么是偏函數(shù)?偏函數(shù)就是將一個(gè) n 參的函數(shù)轉(zhuǎn)換成固定 x 參的函數(shù),剩余參數(shù)(n - x)將在下次調(diào)用全部傳入。舉個(gè)例子:

    function add(a, b, c{
        return a + b + c
    }
    let partialAdd = partial(add, 1)
    partialAdd(23)

    發(fā)現(xiàn)沒(méi)有,其實(shí)偏函數(shù)和函數(shù)柯里化有點(diǎn)像,所以根據(jù)函數(shù)柯里化的實(shí)現(xiàn),能夠能很快寫(xiě)出偏函數(shù)的實(shí)現(xiàn):

    function partial(fn, ...args{
        return (...arg) => {
            return fn(...args, ...arg)
        }
    }

    如上這個(gè)功能比較簡(jiǎn)單,現(xiàn)在我們希望偏函數(shù)能和柯里化一樣能實(shí)現(xiàn)占位功能,比如:

    function clg(a, b, c{
        console.log(a, b, c)
    }
    let partialClg = partial(clg, '_'2)
    partialClg(13)  // 依次打?。?, 2, 3

    _ 占的位其實(shí)就是 1 的位置。相當(dāng)于:partial(clg, 1, 2),然后 partialClg(3)。明白了原理,我們就來(lái)寫(xiě)實(shí)現(xiàn):

    function partial(fn, ...args{
        return (...arg) => {
            args[index] = 
            return fn(...args, ...arg)
        }
    }

    JSONP

    JSONP 核心原理:script 標(biāo)簽不受同源策略約束,所以可以用來(lái)進(jìn)行跨域請(qǐng)求,優(yōu)點(diǎn)是兼容性好,但是只能用于 GET 請(qǐng)求;

    const jsonp = ({ url, params, callbackName }) => {
        const generateUrl = () => {
            let dataSrc = ''
            for (let key in params) {
                if (params.hasOwnProperty(key)) {
                    dataSrc += `${key}=${params[key]}&`
                }
            }
            dataSrc += `callback=${callbackName}`
            return `${url}?${dataSrc}`
        }
        return new Promise((resolve, reject) => {
            const scriptEle = document.createElement('script')
            scriptEle.src = generateUrl()
            document.body.appendChild(scriptEle)
            window[callbackName] = data => {
                resolve(data)
                document.removeChild(scriptEle)
            }
        })
    }

    AJAX

    const getJSON = function(url{
        return new Promise((resolve, reject) => {
            const xhr = XMLHttpRequest ? new XMLHttpRequest() : new ActiveXObject('Mscrosoft.XMLHttp');
            xhr.open('GET', url, false);
            xhr.setRequestHeader('Accept''application/json');
            xhr.onreadystatechange = function({
                if (xhr.readyState !== 4return;
                if (xhr.status === 200 || xhr.status === 304) {
                    resolve(xhr.responseText);
                } else {
                    reject(new Error(xhr.responseText));
                }
            }
            xhr.send();
        })
    }

    實(shí)現(xiàn)數(shù)組原型方法

    forEach

    Array.prototype.forEach2 = function(callback, thisArg{
        if (this == null) {
            throw new TypeError('this is null or not defined')
        }
        if (typeof callback !== "function") {
            throw new TypeError(callback + ' is not a function')
        }
        const O = Object(this)  // this 就是當(dāng)前的數(shù)組
        const len = O.length >>> 0  // 后面有解釋
        let k = 0
        while (k < len) {
            if (k in O) {
                callback.call(thisArg, O[k], k, O);
            }
            k++;
        }
    }

    參考:forEach#polyfill[2]

    O.length >>> 0 是什么操作?就是無(wú)符號(hào)右移 0 位,那有什么意義嘛?就是為了保證轉(zhuǎn)換后的值為正整數(shù)。其實(shí)底層做了 2 層轉(zhuǎn)換,第一是非 number 轉(zhuǎn)成 number 類(lèi)型,第二是將 number 轉(zhuǎn)成 Uint32 類(lèi)型。感興趣可以閱讀 something >>> 0是什么意思?[3]

    map

    基于 forEach 的實(shí)現(xiàn)能夠很容易寫(xiě)出 map 的實(shí)現(xiàn):

    - Array.prototype.forEach2 = function(callback, thisArg) {
    + Array.prototype.map2 = function(callback, thisArg) {
        if (this == null) {
            throw new TypeError('this is null or not defined')
        }
        if (typeof callback !== "function") {
            throw new TypeError(callback + ' is not a function')
        }
        const O = Object(this)
        const len = O.length >>> 0
    -   let k = 0
    +   let k = 0, res = []
        while (k < len) {
            if (k in O) {
    -           callback.call(thisArg, O[k], k, O);
    +           res[k] = callback.call(thisArg, O[k], k, O);
            }
            k++;
        }
    +   return res
    }

    filter

    同樣,基于 forEach 的實(shí)現(xiàn)能夠很容易寫(xiě)出 filter 的實(shí)現(xiàn):

    - Array.prototype.forEach2 = function(callback, thisArg) {
    + Array.prototype.filter2 = function(callback, thisArg) {
        if (this == null) {
            throw new TypeError('this is null or not defined')
        }
        if (typeof callback !== "function") {
            throw new TypeError(callback + ' is not a function')
        }
        const O = Object(this)
        const len = O.length >>> 0
    -   let k = 0
    +   let k = 0, res = []
        while (k < len) {
            if (k in O) {
    -           callback.call(thisArg, O[k], k, O);
    +           if (callback.call(thisArg, O[k], k, O)) {
    +               res.push(O[k])                
    +           }
            }
            k++;
        }
    +   return res
    }

    some

    同樣,基于 forEach 的實(shí)現(xiàn)能夠很容易寫(xiě)出 some 的實(shí)現(xiàn):

    - Array.prototype.forEach2 = function(callback, thisArg) {
    + Array.prototype.some2 = function(callback, thisArg) {
        if (this == null) {
            throw new TypeError('this is null or not defined')
        }
        if (typeof callback !== "function") {
            throw new TypeError(callback + ' is not a function')
        }
        const O = Object(this)
        const len = O.length >>> 0
        let k = 0
        while (k < len) {
            if (k in O) {
    -           callback.call(thisArg, O[k], k, O);
    +           if (callback.call(thisArg, O[k], k, O)) {
    +               return true
    +           }
            }
            k++;
        }
    +   return false
    }

    reduce

    Array.prototype.reduce2 = function(callback, initialValue{
        if (this == null) {
            throw new TypeError('this is null or not defined')
        }
        if (typeof callback !== "function") {
            throw new TypeError(callback + ' is not a function')
        }
        const O = Object(this)
        const len = O.length >>> 0
        let k = 0, acc
        
        if (arguments.length > 1) {
            acc = initialValue
        } else {
            // 沒(méi)傳入初始值的時(shí)候,取數(shù)組中第一個(gè)非 empty 的值為初始值
            while (k < len && !(k in O)) {
                k++
            }
            if (k > len) {
                throw new TypeError'Reduce of empty array with no initial value' );
            }
            acc = O[k++]
        }
        while (k < len) {
            if (k in O) {
                acc = callback(acc, O[k], k, O)
            }
            k++
        }
        return acc
    }

    實(shí)現(xiàn)函數(shù)原型方法

    call

    使用一個(gè)指定的 this 值和一個(gè)或多個(gè)參數(shù)來(lái)調(diào)用一個(gè)函數(shù)。

    實(shí)現(xiàn)要點(diǎn):

    • this 可能傳入 null;
    • 傳入不固定個(gè)數(shù)的參數(shù);
    • 函數(shù)可能有返回值;
    Function.prototype.call2 = function (context{
        var context = context || window;
        context.fn = this;

        var args = [];
        for(var i = 1, len = arguments.length; i < len; i++) {
            args.push('arguments[' + i + ']');
        }

        var result = eval('context.fn(' + args +')');

        delete context.fn
        return result;
    }

    apply

    apply 和 call 一樣,唯一的區(qū)別就是 call 是傳入不固定個(gè)數(shù)的參數(shù),而 apply 是傳入一個(gè)數(shù)組。

    實(shí)現(xiàn)要點(diǎn):

    • this 可能傳入 null;
    • 傳入一個(gè)數(shù)組;
    • 函數(shù)可能有返回值;
    Function.prototype.apply2 = function (context, arr{
        var context = context || window;
        context.fn = this;

        var result;
        if (!arr) {
            result = context.fn();
        } else {
            var args = [];
            for (var i = 0, len = arr.length; i < len; i++) {
                args.push('arr[' + i + ']');
            }
            result = eval('context.fn(' + args + ')')
        }

        delete context.fn
        return result;
    }

    bind

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

    實(shí)現(xiàn)要點(diǎn):

    • bind() 除了 this 外,還可傳入多個(gè)參數(shù);
    • bing 創(chuàng)建的新函數(shù)可能傳入多個(gè)參數(shù);
    • 新函數(shù)可能被當(dāng)做構(gòu)造函數(shù)調(diào)用;
    • 函數(shù)可能有返回值;
    Function.prototype.bind2 = function (context{
        var self = this;
        var args = Array.prototype.slice.call(arguments1);

        var fNOP = function ({};

        var fBound = function ({
            var bindArgs = Array.prototype.slice.call(arguments);
            return self.apply(this instanceof fNOP ? this : context, args.concat(bindArgs));
        }

        fNOP.prototype = this.prototype;
        fBound.prototype = new fNOP();
        return fBound;
    }

    實(shí)現(xiàn) new 關(guān)鍵字

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

    實(shí)現(xiàn)要點(diǎn):

    • new 會(huì)產(chǎn)生一個(gè)新對(duì)象;
    • 新對(duì)象需要能夠訪(fǎng)問(wèn)到構(gòu)造函數(shù)的屬性,所以需要重新指定它的原型;
    • 構(gòu)造函數(shù)可能會(huì)顯示返回;
    function objectFactory({
        var obj = new Object()
        Constructor = [].shift.call(arguments);
        obj.__proto__ = Constructor.prototype;
        var ret = Constructor.apply(obj, arguments);
        
        // ret || obj 這里這么寫(xiě)考慮了構(gòu)造函數(shù)顯示返回 null 的情況
        return typeof ret === 'object' ? ret || obj : obj;
    };

    使用:

    function person(name, age{
        this.name = name
        this.age = age
    }
    let p = objectFactory(person, '布蘭'12)
    console.log(p)  // { name: '布蘭', age: 12 }

    實(shí)現(xiàn) instanceof 關(guān)鍵字

    instanceof 就是判斷構(gòu)造函數(shù)的 prototype 屬性是否出現(xiàn)在實(shí)例的原型鏈上。

    function instanceOf(left, right{
        let proto = left.__proto__
        while (true) {
            if (proto === nullreturn false
            if (proto === right.prototype) {
                return true
            }
            proto = proto.__proto__
        }
    }

    上面的 left.proto 這種寫(xiě)法可以換成 Object.getPrototypeOf(left)。

    實(shí)現(xiàn) Object.create

    Object.create()方法創(chuàng)建一個(gè)新對(duì)象,使用現(xiàn)有的對(duì)象來(lái)提供新創(chuàng)建的對(duì)象的__proto__。

    Object.create2 = function(proto, propertyObject = undefined{
        if (typeof proto !== 'object' && typeof proto !== 'function') {
            throw new TypeError('Object prototype may only be an Object or null.')
        if (propertyObject == null) {
            new TypeError('Cannot convert undefined or null to object')
        }
        function F({}
        F.prototype = proto
        const obj = new F()
        if (propertyObject != undefined) {
            Object.defineProperties(obj, propertyObject)
        }
        if (proto === null) {
            // 創(chuàng)建一個(gè)沒(méi)有原型對(duì)象的對(duì)象,Object.create(null)
            obj.__proto__ = null
        }
        return obj
    }

    實(shí)現(xiàn) Object.assign

    Object.assign2 = function(target, ...source{
        if (target == null) {
            throw new TypeError('Cannot convert undefined or null to object')
        }
        let ret = Object(target) 
        source.forEach(function(obj{
            if (obj != null) {
                for (let key in obj) {
                    if (obj.hasOwnProperty(key)) {
                        ret[key] = obj[key]
                    }
                }
            }
        })
        return ret
    }

    實(shí)現(xiàn) JSON.stringify

    JSON.stringify([, replacer [, space]) 方法是將一個(gè) JavaScript 值(對(duì)象或者數(shù)組)轉(zhuǎn)換為一個(gè) JSON 字符串。此處模擬實(shí)現(xiàn),不考慮可選的第二個(gè)參數(shù) replacer 和第三個(gè)參數(shù) space,如果對(duì)這兩個(gè)參數(shù)的作用還不了解,建議閱讀 MDN[4] 文檔。

    1. 基本數(shù)據(jù)類(lèi)型:
      • undefined 轉(zhuǎn)換之后仍是 undefined(類(lèi)型也是 undefined)
      • boolean 值轉(zhuǎn)換之后是字符串 "false"/"true"
      • number 類(lèi)型(除了 NaN 和 Infinity)轉(zhuǎn)換之后是字符串類(lèi)型的數(shù)值
      • symbol 轉(zhuǎn)換之后是 undefined
      • null 轉(zhuǎn)換之后是字符串 "null"
      • string 轉(zhuǎn)換之后仍是string
      • NaN 和 Infinity 轉(zhuǎn)換之后是字符串 "null"
    2. 函數(shù)類(lèi)型:轉(zhuǎn)換之后是 undefined
    3. 如果是對(duì)象類(lèi)型(非函數(shù))
      • 如果有 toJSON() 方法,那么序列化 toJSON() 的返回值。
      • 如果屬性值中出現(xiàn)了 undefined、任意的函數(shù)以及 symbol 值,忽略。
      • 所有以 symbol 為屬性鍵的屬性都會(huì)被完全忽略掉。
      • 如果是一個(gè)數(shù)組:如果屬性值中出現(xiàn)了 undefined、任意的函數(shù)以及 symbol,轉(zhuǎn)換成字符串 "null" ;
      • 如果是 RegExp 對(duì)象:返回 {} (類(lèi)型是 string);
      • 如果是 Date 對(duì)象,返回 Date 的 toJSON 字符串值;
      • 如果是普通對(duì)象;
    4. 對(duì)包含循環(huán)引用的對(duì)象(對(duì)象之間相互引用,形成無(wú)限循環(huán))執(zhí)行此方法,會(huì)拋出錯(cuò)誤。
    function jsonStringify(data{
        let dataType = typeof data;
        
        if (dataType !== 'object') {
            let result = data;
            //data 可能是 string/number/null/undefined/boolean
            if (Number.isNaN(data) || data === Infinity) {
                //NaN 和 Infinity 序列化返回 "null"
                result = "null";
            } else if (dataType === 'function' || dataType === 'undefined' || dataType === 'symbol') {
                //function 、undefined 、symbol 序列化返回 undefined
                return undefined;
            } else if (dataType === 'string') {
                result = '"' + data + '"';
            }
            //boolean 返回 String()
            return String(result);
        } else if (dataType === 'object') {
            if (data === null) {
                return "null"
            } else if (data.toJSON && typeof data.toJSON === 'function') {
                return jsonStringify(data.toJSON());
            } else if (data instanceof Array) {
                let result = [];
                //如果是數(shù)組
                //toJSON 方法可以存在于原型鏈中
                data.forEach((item, index) => {
                    if (typeof item === 'undefined' || typeof item === 'function' || typeof item === 'symbol') {
                        result[index] = "null";
                    } else {
                        result[index] = jsonStringify(item);
                    }
                });
                result = "[" + result + "]";
                return result.replace(/'/g'"');
                
            } else {
                //普通對(duì)象
                /**
                 * 循環(huán)引用拋錯(cuò)(暫未檢測(cè),循環(huán)引用時(shí),堆棧溢出)
                 * symbol key 忽略
                 * undefined、函數(shù)、symbol 為屬性值,被忽略
                 */

                let result = [];
                Object.keys(data).forEach((item, index) => {
                    if (typeof item !== 'symbol') {
                        //key 如果是symbol對(duì)象,忽略
                        if (data[item] !== undefined && typeof data[item] !== 'function'
                            && typeof data[item] !== 'symbol') {
                            //鍵值如果是 undefined、函數(shù)、symbol 為屬性值,忽略
                            result.push('"' + item + '"' + ":" + jsonStringify(data[item]));
                        }
                    }
                });
                return ("{" + result + "}").replace(/'/g'"');
            }
        }
    }

    參考:實(shí)現(xiàn) JSON.stringify[5]

    實(shí)現(xiàn) JSON.parse

    介紹 2 種方法實(shí)現(xiàn):

    • eval 實(shí)現(xiàn);
    • new Function 實(shí)現(xiàn);

    eval 實(shí)現(xiàn)

    第一種方式最簡(jiǎn)單,也最直觀(guān),就是直接調(diào)用 eval,代碼如下:

    var json = '{"a":"1", "b":2}';
    var obj = eval("(" + json + ")");  // obj 就是 json 反序列化之后得到的對(duì)象

    但是直接調(diào)用 eval 會(huì)存在安全問(wèn)題,如果數(shù)據(jù)中可能不是 json 數(shù)據(jù),而是可執(zhí)行的 JavaScript 代碼,那很可能會(huì)造成 XSS 攻擊。因此,在調(diào)用 eval 之前,需要對(duì)數(shù)據(jù)進(jìn)行校驗(yàn)。

    var rx_one = /^[\],:{}\s]*$/;
    var rx_two = /\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g;
    var rx_three = /"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g;
    var rx_four = /(?:^|:|,)(?:\s*\[)+/g;

    if (
        rx_one.test(
            json.replace(rx_two, "@")
                .replace(rx_three, "]")
                .replace(rx_four, "")
        )
    ) {
        var obj = eval("(" +json + ")");
    }

    參考:JSON.parse 三種實(shí)現(xiàn)方式[6]

    new Function 實(shí)現(xiàn)

    Function 與 eval 有相同的字符串參數(shù)特性。

    var json = '{"name":"小姐姐", "age":20}';
    var obj = (new Function('return ' + json))();

    實(shí)現(xiàn) Promise

    實(shí)現(xiàn) Promise 需要完全讀懂 Promise A+ 規(guī)范[7],不過(guò)從總體的實(shí)現(xiàn)上看,有如下幾個(gè)點(diǎn)需要考慮到:

    • then 需要支持鏈?zhǔn)秸{(diào)用,所以得返回一個(gè)新的 Promise;
    • 處理異步問(wèn)題,所以得先用 onResolvedCallbacks 和 onRejectedCallbacks 分別把成功和失敗的回調(diào)存起來(lái);
    • 為了讓鏈?zhǔn)秸{(diào)用正常進(jìn)行下去,需要判斷 onFulfilled 和 onRejected 的類(lèi)型;
    • onFulfilled 和 onRejected 需要被異步調(diào)用,這里用 setTimeout 模擬異步;
    • 處理 Promise 的 resolve;
    const PENDING = 'pending';
    const FULFILLED = 'fulfilled';
    const REJECTED = 'rejected';

    class Promise {
        constructor(executor) {
            this.status = PENDING;
            this.value = undefined;
            this.reason = undefined;
            this.onResolvedCallbacks = [];
            this.onRejectedCallbacks = [];
            
            let resolve = (value) = > {
                if (this.status === PENDING) {
                    this.status = FULFILLED;
                    this.value = value;
                    this.onResolvedCallbacks.forEach((fn) = > fn());
                }
            };
            
            let reject = (reason) = > {
                if (this.status === PENDING) {
                    this.status = REJECTED;
                    this.reason = reason;
                    this.onRejectedCallbacks.forEach((fn) = > fn());
                }
            };
            
            try {
                executor(resolve, reject);
            } catch (error) {
                reject(error);
            }
        }
        
        then(onFulfilled, onRejected) {
            // 解決 onFufilled,onRejected 沒(méi)有傳值的問(wèn)題
            onFulfilled = typeof onFulfilled === "function" ? onFulfilled : (v) = > v;
            // 因?yàn)殄e(cuò)誤的值要讓后面訪(fǎng)問(wèn)到,所以這里也要拋出錯(cuò)誤,不然會(huì)在之后 then 的 resolve 中捕獲
            onRejected = typeof onRejected === "function" ? onRejected : (err) = > {
                throw err;
            };
            // 每次調(diào)用 then 都返回一個(gè)新的 promise
            let promise2 = new Promise((resolve, reject) = > {
                if (this.status === FULFILLED) {
                    //Promise/A+ 2.2.4 --- setTimeout
                    setTimeout(() = > {
                        try {
                            let x = onFulfilled(this.value);
                            // x可能是一個(gè)proimise
                            resolvePromise(promise2, x, resolve, reject);
                        } catch (e) {
                            reject(e);
                        }
                    }, 0);
                }
            
                if (this.status === REJECTED) {
                    //Promise/A+ 2.2.3
                    setTimeout(() = > {
                        try {
                            let x = onRejected(this.reason);
                            resolvePromise(promise2, x, resolve, reject);
                        } catch (e) {
                            reject(e);
                        }
                    }, 0);
                }
                
                if (this.status === PENDING) {
                    this.onResolvedCallbacks.push(() = > {
                        setTimeout(() = > {
                            try {
                                let x = onFulfilled(this.value);
                                resolvePromise(promise2, x, resolve, reject);
                            } catch (e) {
                                reject(e);
                            }
                        }, 0);
                    });
                
                    this.onRejectedCallbacks.push(() = > {
                        setTimeout(() = > {
                            try {
                                let x = onRejected(this.reason);
                                resolvePromise(promise2, x, resolve, reject);
                            } catch (e) {
                                reject(e);
                            }
                        }, 0);
                    });
                }
            });
            
            return promise2;
        }
    }
    const resolvePromise = (promise2, x, resolve, reject) = > {
        // 自己等待自己完成是錯(cuò)誤的實(shí)現(xiàn),用一個(gè)類(lèi)型錯(cuò)誤,結(jié)束掉 promise  Promise/A+ 2.3.1
        if (promise2 === x) {
            return reject(
                new TypeError("Chaining cycle detected for promise #<Promise>"));
        }
        // Promise/A+ 2.3.3.3.3 只能調(diào)用一次
        let called;
        // 后續(xù)的條件要嚴(yán)格判斷 保證代碼能和別的庫(kù)一起使用
        if ((typeof x === "object" && x != null) || typeof x === "function") {
            try {
                // 為了判斷 resolve 過(guò)的就不用再 reject 了(比如 reject 和 resolve 同時(shí)調(diào)用的時(shí)候)  Promise/A+ 2.3.3.1
                let then = x.then;
                if (typeof then === "function") {
                // 不要寫(xiě)成 x.then,直接 then.call 就可以了 因?yàn)?nbsp;x.then 會(huì)再次取值,Object.defineProperty  Promise/A+ 2.3.3.3
                    then.call(
                        x, (y) = > {
                            // 根據(jù) promise 的狀態(tài)決定是成功還是失敗
                            if (called) return;
                            called = true;
                            // 遞歸解析的過(guò)程(因?yàn)榭赡?nbsp;promise 中還有 promise) Promise/A+ 2.3.3.3.1
                            resolvePromise(promise2, y, resolve, reject);
                        }, (r) = > {
                            // 只要失敗就失敗 Promise/A+ 2.3.3.3.2
                            if (called) return;
                            called = true;
                            reject(r);
                        });
                } else {
                    // 如果 x.then 是個(gè)普通值就直接返回 resolve 作為結(jié)果  Promise/A+ 2.3.3.4
                    resolve(x);
                }
            } catch (e) {
                // Promise/A+ 2.3.3.2
                if (called) return;
                called = true;
                reject(e);
            }
        } else {
            // 如果 x 是個(gè)普通值就直接返回 resolve 作為結(jié)果  Promise/A+ 2.3.4
            resolve(x);
        }
    };

    Promise 寫(xiě)完之后可以通過(guò) promises-aplus-tests 這個(gè)包對(duì)我們寫(xiě)的代碼進(jìn)行測(cè)試,看是否符合 A+ 規(guī)范。不過(guò)測(cè)試前還得加一段代碼:

    // promise.js
    // 這里是上面寫(xiě)的 Promise 全部代碼
    Promise.defer = Promise.deferred = function ({
        let dfd = {}
        dfd.promise = new Promise((resolve,reject)=>{
            dfd.resolve = resolve;
            dfd.reject = reject;
        });
        return dfd;
    }
    module.exports = Promise;

    全局安裝:

    npm i promises-aplus-tests -g

    終端下執(zhí)行驗(yàn)證命令:

    promises-aplus-tests promise.js

    上面寫(xiě)的代碼可以順利通過(guò)全部 872 個(gè)測(cè)試用例。

    參考:

    • BAT前端經(jīng)典面試問(wèn)題:史上最最最詳細(xì)的手寫(xiě)Promise教程[8]
    • 100 行代碼實(shí)現(xiàn) Promises/A+ 規(guī)范[9]

    Promise.resolve

    Promsie.resolve(value) 可以將任何值轉(zhuǎn)成值為 value 狀態(tài)是 fulfilled 的 Promise,但如果傳入的值本身是 Promise 則會(huì)原樣返回它。

    Promise.resolve = function(value{
        // 如果是 Promsie,則直接輸出它
        if(value instanceof Promise){
            return value
        }
        return new Promise(resolve => resolve(value))
    }

    參考:深入理解 Promise[10]

    Promise.reject

    和 Promise.resolve() 類(lèi)似,Promise.reject() 會(huì)實(shí)例化一個(gè) rejected 狀態(tài)的 Promise。但與 Promise.resolve() 不同的是,如果給 Promise.reject() 傳遞一個(gè) Promise 對(duì)象,則這個(gè)對(duì)象會(huì)成為新 Promise 的值。

    Promise.reject = function(reason{
        return new Promise((resolve, reject) => reject(reason))
    }

    Promise.all

    Promise.all 的規(guī)則是這樣的:

    • 傳入的所有 Promsie 都是 fulfilled,則返回由他們的值組成的,狀態(tài)為 fulfilled 的新 Promise;
    • 只要有一個(gè) Promise 是 rejected,則返回 rejected 狀態(tài)的新 Promsie,且它的值是第一個(gè) rejected 的 Promise 的值;
    • 只要有一個(gè) Promise 是 pending,則返回一個(gè) pending 狀態(tài)的新 Promise;
    Promise.all = function(promiseArr{
        let index = 0, result = []
        return new Promise((resolve, reject) => {
            promiseArr.forEach((p, i) => {
                Promise.resolve(p).then(val => {
                    index++
                    result[i] = val
                    if (index === promiseArr.length) {
                        resolve(result)
                    }
                }, err => {
                    reject(err)
                })
            })
        })
    }

    Promise.race

    Promise.race 會(huì)返回一個(gè)由所有可迭代實(shí)例中第一個(gè) fulfilled 或 rejected 的實(shí)例包裝后的新實(shí)例。

    Promise.race = function(promiseArr{
        return new Promise((resolve, reject) => {
            promiseArr.forEach(p => {
                Promise.resolve(p).then(val => {
                    resolve(val)
                }, err => {
                    rejecte(err)
                })
            })
        })
    }

    Promise.allSettled

    Promise.allSettled 的規(guī)則是這樣:

    • 所有 Promise 的狀態(tài)都變化了,那么新返回一個(gè)狀態(tài)是 fulfilled 的 Promise,且它的值是一個(gè)數(shù)組,數(shù)組的每項(xiàng)由所有 Promise 的值和狀態(tài)組成的對(duì)象;
    • 如果有一個(gè)是 pending 的 Promise,則返回一個(gè)狀態(tài)是 pending 的新實(shí)例;
    Promise.allSettled = function(promiseArr{
        let result = []
            
        return new Promise((resolve, reject) => {
            promiseArr.forEach((p, i) => {
                Promise.resolve(p).then(val => {
                    result.push({
                        status'fulfilled',
                        value: val
                    })
                    if (result.length === promiseArr.length) {
                        resolve(result) 
                    }
                }, err => {
                    result.push({
                        status'rejected',
                        reason: err
                    })
                    if (result.length === promiseArr.length) {
                        resolve(result) 
                    }
                })
            })  
        })   
    }

    Promise.any

    Promise.any 的規(guī)則是這樣:

    • 空數(shù)組或者所有 Promise 都是 rejected,則返回狀態(tài)是 rejected 的新 Promsie,且值為 AggregateError 的錯(cuò)誤;
    • 只要有一個(gè)是 fulfilled 狀態(tài)的,則返回第一個(gè)是 fulfilled 的新實(shí)例;
    • 其他情況都會(huì)返回一個(gè) pending 的新實(shí)例;
    Promise.any = function(promiseArr{
        let index = 0
        return new Promise((resolve, reject) => {
            if (promiseArr.length === 0return 
            promiseArr.forEach((p, i) => {
                Promise.resolve(p).then(val => {
                    resolve(val)
                    
                }, err => {
                    index++
                    if (index === promiseArr.length) {
                      reject(new AggregateError('All promises were rejected'))
                    }
                })
            })
        })
    }

    后話(huà)

    能看到這里的對(duì)代碼都是真愛(ài)了,畢竟代碼這玩意看起來(lái)是真的很枯燥,但是如果看懂了后,就會(huì)像打游戲贏(yíng)了一樣開(kāi)心,而且這玩意會(huì)上癮,當(dāng)你通關(guān)了越多的關(guān)卡后,你的能力就會(huì)拔高一個(gè)層次。用標(biāo)題的話(huà)來(lái)說(shuō)就是:搞懂后,提升真的大。加油吧??,干飯人

    噢不,代碼人。




    參考資料

    [1]

    圖片懶加載: https://juejin.cn/post/6844903856489365518#heading-19

    [2]

    forEach#polyfill: https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Array/forEach#polyfill

    [3]

    something >>> 0是什么意思: https://zhuanlan.zhihu.com/p/100790268

    [4]

    stringify: https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/JSON/stringify

    [5]

    實(shí)現(xiàn) JSON.stringify: https://github.com/YvetteLau/Step-By-Step/issues/39#issuecomment-508327280

    [6]

    JSON.parse 三種實(shí)現(xiàn)方式: https://github.com/youngwind/blog/issues/115#issue-300869613

    [7]

    Promise A+ 規(guī)范: https://promisesaplus.com/

    [8]

    BAT前端經(jīng)典面試問(wèn)題:史上最最最詳細(xì)的手寫(xiě)Promise教程: https://juejin.cn/post/6844903625769091079

    [9]

    100 行代碼實(shí)現(xiàn) Promises/A+ 規(guī)范: https://mp.weixin.qq.com/s/qdJ0Xd8zTgtetFdlJL3P1g

    [10]

    深入理解 Promise: https://bubuzou.com/2020/10/22/promise/



             學(xué)習(xí)前端關(guān)注 前端Sharing  持續(xù)推送高質(zhì)量好文



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

    手機(jī)掃一掃分享

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

    手機(jī)掃一掃分享

    分享
    舉報(bào)
    <p id="m2nkj"><option id="m2nkj"><big id="m2nkj"></big></option></p>
    <strong id="m2nkj"></strong>
    <ruby id="m2nkj"></ruby>

    <var id="m2nkj"></var>
  • 草草久久久亚洲AV | 免费观看性感美女被操逼视频网站 | 婷婷淫| 性爱av无码 | 人妻天天爽| 中文字幕日韩有码 | 国产极品艳情生活视频在线播放 | 天堂资源站 | 久久短视频 | 欧美色色网 |