「前端發動機」原生 JavaScript 手寫各類 數組 API

前言

JavaScript 中的數組類型提供了不少原生方法供咱們使用,本文會 模擬實現 一些經常使用的數組 API。javascript

另外我本身也是在不斷的學習中,若是有不對的地方麻煩你們斧正,我會及時更新,感謝。前端

博客地址 🍹🍰 fe-codevue

API

數組的 API 有不少,我這裏放一些經常使用的。若是你們有其餘的實現方法,能夠放在評論區,我看到了會更新到文章中 ^_^。java

本文不是具體講某個 API 的基本用法,因此對這些 API 用法不太熟悉的同窗須要先自行學習。另外大部分實現,在 MDN 上都有。 node

前往 —> MDN 學習基礎用法。git

在正式開始實現以前,先看一個例子。github

let arr = [];
arr[3] = 3;
// arr.length ? arr[0] ? 0 in arr ?

// 4 undefined fasle
複製代碼

這個東西在後面的實現中會出現,因此你們先了解一下。數組的下標不必定是連續的,直接賦值還會影響它的長度。面試

forEach

  • 簡單實現
// forEach 支持傳入兩個參數,callback、thisArg
// callback 返回3個參數,當前元素、當前元素索引、原數組
// thisArg 傳入後,改變 callback 的 this 指針
Array.prototype.myforeach = function (fn, context = null) {
    let index = 0;
    let arr = this;
    if (typeof fn !== 'function') {
        throw new TypeError(fn + ' is not a function');
    }
    while (index < arr.length) {
        if (index in arr) { // 數組的下標並不必定是連續的
            fn.call(context, arr[index], index, arr);
        }
        index ++;
    }
};
複製代碼
  • 支持 async/await

以前見大佬們討論過這個問題,因此提一下。forEach 在正常狀況像下面這麼寫確定是作不到同步的,程序不會等一個循環中的異步完成再進行下一個循環。緣由很明顯,在上面的模擬中,while 循環只是簡單執行了 callback,因此儘管 callback 內使用了 await ,也只是影響到 callback 內部。mongodb

arr.myforeach(async v => {
    await fetch(v);
});
複製代碼

要支持上面這種寫法,只要稍微改一下就好。數組

Array.prototype.myforeach = async function (fn, context = null) {
    let index = 0;
    let arr = this;
    if (typeof fn !== 'function') {
        throw new TypeError(fn + ' is not a function');
    }
    while (index < arr.length) {
        if (index in arr) {
            try {
                await fn.call(context, arr[index], index, arr);
            } catch (e) {
                console.log(e);
            }
        }
        index ++;
    }
};
複製代碼

map

map 的實現大致和 forEach 相似,只是返回了一個新數組。

// 參數和forEach同樣
// callback 須要有一個返回值
Array.prototype.mymap = function (fn, context = null) {
    let arr = this;
    let len = arr.length;
    let index = 0;
    let newArr = [];
    if (typeof fn !== 'function') {
        throw new TypeError(fn + ' is not a function');
    }
    while (index < len) {
        if (index in arr) {
            let result = fn.call(context, arr[index], index, arr);
            newArr[index] = result; // 返回值做爲一個新數組
        }
        index ++;
    }
    return newArr;
};
複製代碼

reduce

reduce 稍微麻煩一些,須要根據第二個參數是否存在,使用不一樣的處理方式。

Array.prototype.myreduce = function (...arg) {
    let arr = this;
    let len = arr.length;
    let index = 0;
    let fn = arg[0], result;
    if (arg.length >= 2) { // 判斷是否有第二個參數,有的話做爲回調函數運行的初始值
        result = arg[1];
    } else {
        // reduce 在沒有第二個參數的時候,會把數組的第一項做爲回調的初始值
        // 第一項並不必定是 a[0]
        while (index < len && !(index in arr)) {
        // 下標小於數組長度且下標不屬於該數組就一直循環,用來找到數組的第一項
            index++;
        }
        if (index >= len) { // 若是第一項大於等於數組長度,則說明是空數組
            throw new TypeError( '空數組且沒有初始值' );
        }
        result = arr[index++]; // 賦值以後下標+1
    }
    if (typeof fn !== 'function') {
        throw new TypeError(fn + ' is not a function');
    }
    while (index < len) {
        if (index in arr) {
            result = fn(result, arr[index], index, arr); // 每次回調的返回值,都會傳入下次回調
        }
        index ++;
    }
    return result;
};
複製代碼

reduce 實現一個 map

常常會有面試問到這道題,順便寫一下。

Array.prototype.mapByreduce = function (fn, context = null) {
    let arr = this;
    if (typeof fn !== 'function') {
         throw new TypeError(fn + ' is not a function');
    }
    return arr.reduce((pre, cur, index, array) => {
        let res = fn.call(context, cur, index, array);
        return [...pre, res]; // 返回一個新數組
    }, []);
};
複製代碼

filter

filter 通常用來篩選。

Array.prototype.myfilter = function (fn, context = null) {
    let arr = this;
    let len = arr.length;
    let index = 0, k = 0;
    let newArr = [];
    if (typeof fn !== 'function') {
        throw new TypeError(fn + ' is not a function');
    }
    while (index < len) {
        if (index in arr) {
            let result = fn.call(context, arr[index], index, arr);
            if (result) newArr[k++] = arr[index]; // 若是返回值爲真,就添加進新數組
        }
        index ++;
    }
    return newArr;
};
複製代碼

find 和 findIndex

find 和 filter 很相似,找到一個就返回當前元素,找不到返回 undefined。

findIndex 找到返回下標,找不到返回 -1。和 indexOf 相似,區別是支持回調。

Array.prototype.myfind = function (fn, context = null) {
    let arr = this;
    let len = arr.length;
    let index = 0;
    if (typeof fn !== 'function') {
        throw new TypeError(fn + ' is not a function');
    }
    while (index < len) {
        if (index in arr) {
            let result = fn.call(context, arr[index], index, arr);
            if (result) return arr[index]; // 知足條件就返回
        }
        index ++;
    }
    return undefined;
};
複製代碼

some

some 和 find,除了返回值有區別,其餘的能夠說都同樣。

Array.prototype.mysome = function (fn, context = null) {
    let arr = this;
    let len = arr.length;
    let index = 0;
    if (typeof fn !== 'function') {
        throw new TypeError(fn + ' is not a function');
    }
    while (index < len) {
        if (index in arr) {
            let result = fn.call(context, arr[index], index, arr);
            if (result) return true; // 找到一個知足的,當即返回true
        }
        index ++;
    }
    return false; // 找不到返回 false
};
複製代碼

every

跟 some 相比,每一個成員都知足條件才返回 true,有一個不知足就返回 false。

Array.prototype.myevery = function (fn, context = null) {
    let arr = this;
    let len = arr.length;
    let index = 0;
    if (typeof fn !== 'function') {
        throw new TypeError(fn + ' is not a function');
    }
    while (index < len) {
        if (index in arr) {
            let result = fn.call(context, arr[index], index, arr);
            if (!result) return false; // 有一個不知足,就返回false
        }
        index ++;
    }
    return true;
};
複製代碼

剛剛接連幾個 filter、find、some、every 在實現和功能上都很類似,只是返回值上有一些差異,因此更要在合適的場景使用合適的方法。

includes 和 indexOf

這兩個均可以用來查找數組中是否有某個元素,只是返回值有區別。

Array.prototype.myincludes = function (val, fromIndex = 0) {
    let arr = this;
    let len = arr.length;
    let k = Math.max(fromIndex >= 0 ? fromIndex : len - Math.abs(fromIndex), 0);
    // 容許傳入負數,意爲從倒數第幾位開始查找
    // 負數依然是按升序查找
    // 避免傳入負數絕對值大於len而使k出現負數,k設置最小值 0 
    function check(x, y) {
        return x === y ||
        (typeof x === 'number' && typeof y === 'number' && isNaN(x) && isNaN(y));
        // 判斷 NaN
    }
    while (k < len) {
        if (k in arr) {
            if (check(val, arr[k])) return true; // 找到一個符合條件的,返回 true
        }
        k ++;
    }
    return false; // 沒找到 返回false
};
複製代碼
// indexOf 不支持查找 NaN 
Array.prototype.myindexOf = function (val, fromIndex = 0) {
    let arr = this;
    let len = arr.length;
    let k = Math.max(fromIndex >= 0 ? fromIndex : len - Math.abs(fromIndex), 0);
    // 處理負數
    while (k < len) {
        if (k in arr) {
            if (val === arr[k]) return k; // 找到返回下標
        }
        k ++;
    }
    return -1; // 找不到返回 -1
};
複製代碼

join

使用鏈接符,將數組轉成字符串

Array.prototype.myjoin = function (connector = ',') {
    let arr = this;
    let len = arr.length;
    let str = '';
    let k = 0;
    while (k < len) {
        if (k in arr) {
            if (k === len -1) { // 最後一位不用鏈接
                str += arr[k];
            } else {
                str += arr[k] + connector.toString();
            }
        }
        k ++;
    }
    return str;
};
複製代碼

好了,大體就寫這些,若是你們以爲有必要補充的能夠跟我說。

參考文章

交流羣

qq前端交流羣:960807765,歡迎各類技術交流,期待你的加入;

微信羣:有須要的同窗能夠加我好友,我拉你入羣,文末有二維碼 ^_^。

後記

若是你看到了這裏,且本文對你有一點幫助的話,但願你能夠動動小手支持一下做者,感謝🍻。文中若有不對之處,也歡迎你們指出,共勉。好了,又耽誤你們的時間了,感謝閱讀,下次再見!

近期文章:

感興趣的同窗能夠關注下個人公衆號 前端發動機,好玩又有料。也可加我好友,你們一塊兒學習交流 ^_^。

相關文章
相關標籤/搜索