JS 數組常見操做彙總,數組去重、降維、排序、多數組合並實現思路整理

壹 ❀ 引

JavaScript開發中數組加工極爲常見,其次在面試中被問及的機率也特別高,一直想整理一篇關於數組常見操做的文章,本文也算了卻心願了。javascript

說在前面,文中的實現並不是最佳,實現雖然有不少種,但我以爲你們至少應該掌握一種,這樣在面試能解決大部分數組問題。在瞭解實現思路後,平常開發中結合實際場景優化實現,提高性能也是後期該考慮的。html

本文主要圍繞數組去重、數組排序、數組降維、數組合並、數組過濾、數組求差集,並集,交集,數組是否包含某項等知識點展開,附帶部分知識拓展,在看實現代碼前也建議你們先自行思考,那麼本文開始。java

貳 ❀ 常見數組操做

貳 ❀ 壹 數組去重

數組去重我分爲兩種狀況,簡單數組去重與對象數組去重。所謂簡單數組即元素均爲基本數據類型,以下:面試

let arr = [undefined, 0, 1, 2, 2, 3, 4, 0, undefined];
let arr_ = arr.filter((self, index, arr) => index === arr.indexOf(self));
console.log(arr_); //[undefined, 0, 1, 2, 3, 4]

有沒有更簡單的作法?有的同窗確定想到了ES6新增的Set數據結構,這也是去重的妙招,原理是Set結構不接受重複值,以下:算法

[...new Set([undefined, 0, 1, 2, 2, 3, 4, 0, undefined])]//[undefined, 0, 1, 2, 3, 4]

對象數組顧名思義,每一個元素都是一個對象,好比咱們但願去除掉name屬性相同的對象:數組

let arr = [{name:'echo'},{name:'聽風是風'},{name:'echo'},{name:'時間跳躍'}];
let keys = {};
let arr_ = arr.reduce((accumulator,currentValue)=>{
     !keys[currentValue['name']] ?
      keys[currentValue['name']] = true && accumulator.push(currentValue) :
      null;
     return accumulator;
},[]);
console.log(arr_);//[{name:'echo'},{name:'聽風是風'},{name:'時間跳躍'}]

思路並不難,咱們藉助一個空對象keys,將每次出現過的對象的name值做爲key,並將其設置爲true;那麼下次出現時根據三元判斷天然會跳過push操做,從而達到去重目的。瀏覽器

reduce存在必定兼容問題,至少徹底不兼容IE,不過咱們知道了這個思路,即便使用forEach一樣能作到上面的效果,改寫就留給你們了。數據結構

有同窗確定就想到了,能不能使用Set去重對象數組呢?其實並不能,由於對於JavaScript來講,兩個長得相同的對象只是外觀相同,它們的引用地址並不一樣,好比:app

[1,2,3]===[1,2,3]//false

因此對於Set結構而言,它們就是不一樣的兩個值,好比下面這個例子:函數

[...new Set([{name:'echo'},{name:'echo'}])]//{name:'echo'},{name:'echo'}

淺拷貝可讓兩個對象徹底相等,以下:

let a=[1,2];
let b = a;
console.log(a===b);//true

因此咱們能夠用new Set()去重引用地址相同的對象:

let a = {name:'echo'};
let b = a;
console.log([...new Set([a,b])]); //{name: "echo"}

大概這麼個意思,關於數組去重先說到這。

貳 ❀ 貳 數組降維

數組降維什麼意思?舉個例子,將二維數組[[1,2],[3,4]]轉變爲一維數組[1,2,3,4 ]

ES6中新增了數組降維方法flat,使用比較簡單,好比就上面的例子能夠這麼作:

let arr = [[1,2],[3,4]];
let arr_ = arr.flat();
console.log(arr_);//[1, 2, 3, 4]

若是是三維數組怎麼辦呢?falt方法接受一個參數表示降維的層數,默認爲1,你能夠理解爲要去掉 [] 的層數。

三維數組降維能夠這麼寫:

let arr = [[1,2],[3,4],[5,[6]]];
let arr_ = arr.flat(2);
console.log(arr_);//[1, 2, 3, 4, 5, 6]

若是你不知道數組要降維的層數,你能夠直接將參數設置爲infinity(無限大),這樣無論你是幾維都會被降爲一維數組:

let arr = [[[[[1,2]]]]];
let arr_ = arr.flat(Infinity);
console.log(arr_);//[1, 2]

簡單粗暴,好用是好用,兼容也是個大問題,谷歌版本從69才徹底支持,其它瀏覽器天然沒得說。

咱們能夠簡單模擬flat實現,以下:

let arr = [0, [1],
    [2, 3],
    [4, [5, 6, 7]]
];

function flat_(arr) {
    if (!Array.isArray(arr)) {
        throw new Error('The argument must be an array.');
    };
    let arr_ = [];
    arr.forEach((self) => {
        Array.isArray(self) ?
            arr_.push.apply(arr_, flat_(self)) :
            arr_.push(self);
    });
    return arr_;
};
flat_(arr); //[0, 1, 2, 3, 4, 5, 6, 7]

在這個實現中,巧妙使用apply參數接受數組的特色,讓push也能扁平化接受一個一維數組,從而達到數組合並的目的。

換種思路,使用reduce結合concat方法,實現能夠更簡單一點點,以下:

function flat_(arr) {
    if (!Array.isArray(arr)) {
        throw new Error('The argument must be an array.');
    };
    return arr.reduce((accumulator, currentValue) => {
        return accumulator.concat(Array.isArray(currentValue) ? flat_(currentValue) : currentValue);
    }, []);
};
console.log(flat_(arr));//[0, 1, 2, 3, 4, 5, 6, 7]

這個實現也只是省略了建立新數組與返回新數組兩行代碼,這兩個操做reduce都幫咱們作了。

實現一依賴的是push,實現二依賴的是concat,同爲數組方法,這裏說幾個你們容易忽略的知識點。

concat除了能合併數組,其實也能合併簡單類型數據,實現二中正是利用了這一點:

[1,2,3].concat([4]);//[1,2,3,4]
[1,2,3].concat(4);//[1,2,3,4]

concat返回合併後的新數組,而push返回添加操做後數組的長度

let a = [1,2,3].concat([4]);
console.log(a);//[1,2,3,4]
let b = [1,2,3].push(4);
console.log(b);//4

concat屬於淺拷貝,這是不少人都容易誤解的一個點,一個誤解的例子:

let arr = [1,2,3];
let a = arr.concat();
arr[0] = 0;
console.log(a);//[1, 2, 3]

而在下面這個例子中,你會發現concat確實是淺拷貝:

let arr_ = [[1,2],[3]];
let a_ = arr_.concat();
arr_[0][0] = 0;
console.log(a_);//[[0,2],[3]]

這是爲何?在MDN文檔說明中解釋的很清楚,concat建立一個新數組,新數組由被調用的數組元素組成,且元素順序與原數組保持一致。元素複製操做中分爲基本類型與引用類型兩種狀況:

數據類型如字符串,數字和布爾(不是StringNumberBoolean 對象):concat將字符串和數字的值複製到新數組中。

對象引用(而不是實際對象):concat將對象引用複製到新數組中。 原始數組和新數組都引用相同的對象。 也就是說,若是引用的對象被修改,則更改對於新數組和原始數組都是可見的。 這包括也是數組的數組參數的元素。

有人以爲concat是深拷貝,也是由於數組中的元素剛好是基本數據類型,這點但願你們謹記。那麼關於數組降維就說到這裏了。

貳 ❀ 叄 數組合並、多數組合並

在介紹數組降維時咱們順帶說起了數組合並的一些作法,若是隻是合併兩個數組咱們能夠這樣作:

let arr1 = [1, 2];
let arr2 = [3, 4];
arr1.concat(arr2); //[1,2,3,4]

arr1.push.apply(arr1, arr2);
arr1; //[1,2,3,4]

Array.prototype.concat.apply(arr1, arr2); //[1,2,3,4]

那若是是未知個數的數組須要合併怎麼作呢?使用ES6寫法很是簡單:

let arr1 = [1, 2],
    arr2 = [3, 4],
    arr3 = [5, 6];

function concat_(...rest) {
    return [...rest].flat();
};
concat_(arr1, arr2, arr3); //[1, 2, 3, 4, 5, 6]

這裏一共只作了兩件事,使用函數rest參數配合拓展運算符...將三個數組組成成一個二維數組,再利用flat降維。

固然考慮兼容問題,咱們能夠保守一點這麼去寫:

let arr1 = [1, 2],
    arr2 = [3, 4],
    arr3 = [5, 6];

function concat_() {
    let arr_ = Array.prototype.slice.call(arguments);
    let result = [];
    arr_.forEach(self => {
        result.push.apply(result, self);
    });
    return result;
};
concat_(arr1, arr2, arr3); //[1, 2, 3, 4, 5, 6]

有同窗必定在想,爲何forEach內不直接使用result.concat(self)解決合併呢?緣由有兩點:

  • concat不修改原數組而是返回一個新數組,因此循環屢次result仍是空數組。

  • forEach不支持return,沒法將合併過的數組返回供下次繼續合併,這兩個問題使用reduce都能解決。

貳 ❀ 肆 數組排序

這個天然不用說了,我想你們首先想到的天然是sort排序,直接上代碼:

//升序
[1, 0, 2, 5, 4, 3].sort((a, b) => a - b); //[0,1,2,3,4,5]
//降序
[1, 0, 2, 5, 4, 3].sort((a, b) => b - a); //[5,4,3,2,1,0]

那麼問題就來了,雖然咱們知道sort是按字符編碼的順序進行排序,那麼上述代碼中的回調函數起到了什麼做用?其實這一點在JavaScript權威指南中給出了答案:

若想讓sort按照其它方式而非字母表順序進行數組排序,必須給sort方法傳遞一個比較函數。該函數決定了它的兩個參數在排好序的數組中的前後順序,假設第一個參數應該在前,比較函數應該返回一個小於0的數值;相反,假設第一個參數應該在後,函數應該返回一個大於0的數值。而且,假設兩個值相等,函數應該返回0;

什麼意思呢?以上面的a - b爲例,由於ab均爲數字,因此計算結果只能是正數,0,負數三種狀況,若是爲負數則a排在b前面,若是相等,ab順序不變,若是爲正數,a排在b後面,大概這個意思。

咱們將問題升級,如今須要按照年齡從小到大對用戶進行排序,能夠這麼作:

var arr = [{
    name: 'echo',
    age: 18
}, {
    name: '聽風是風',
    age: 26
}, {
    name: '時間跳躍',
    age: 10
}, {
    name: '行星飛行',
    age: 16
}];
arr.sort((a, b) => {
    var a_ = a.age;
    var b_ = b.age;
    return a_ - b_;
});

比較巧的是上面2個例子參與比較的元素都爲數字,因此能參與計算比較,前面已經說了sort方法默認是按照字符編碼的順序進行排序:

['c', 'b', 'a', 'e', 'd'].sort();//["a", "b", "c", "d", "e"]

如今要求以上字母按z-a倒序排列,怎麼作?雖然字母沒法計算,但仍是有大小之分,仍是同樣的作法,以下:

['c', 'b', 'a', 'e', 'd'].sort((a, b) => {
    let result;
    if (a < b) {
        result = 1;
    } else if (a > b) {
        result = -1;
    } else {
        result = 0;
    };
    return result;
}); //["e", "d", "c", "b", "a"]

在介紹sort回調含義的時候已有解釋,若但願從小到大排列,a<b應該返回小於0的數字,但咱們但願排序是由大到小,因此反過來就能夠了,讓a<b時返回大於0的數字,a>b返回小於0的數字,這樣就能夠實現倒序排列。

我知道,關於排序你們都有聽過冒泡、插入等十大經典排序算法,由於篇幅問題這裏就不貼代碼了,若是時間容許我會專門寫一篇簡單易懂的十大排序的文章,那麼關於排序就說到這裏了。

貳 ❀ 伍 數組過濾

數組過濾在開發中即爲常見,咱們通常遇到兩種狀況,一是將符合條件的元素篩選出來,包含在一個新數組中供後續使用;二是將符合條件的元素從原數組中剔除。

咱們先說說第一種狀況,篩選符合條件的元素,實現不少種,首推filter,正如單詞含義同樣用於過濾:

// 篩選3的倍數
[1, 2, 3, 4, 5, 6, 7, 8, 9].filter(self => self % 3 === 0);//[3,6,9]

第二種刪除符合條件的元素,這裏可使用for循環:

// 剔除3的倍數
let arr = [1, 2, 3, 4, 5, 6, 7, 8, 9],
    i = 0,
    length = arr.length;

for (; i < length; i++) {
    // 刪除數組中全部的1
    if (arr[i] % 3 === 0) {
        arr.splice(i, 1);
        //重置i,不然i會跳一位
        i--;
    };
};
console.log(arr);//[1, 2, 4, 5, 7, 8]

咱們換種思路,剔除數組中3的倍數不就是在找不是3的倍數的元素嗎,因此仍是可使用filter作到這一點:

[1, 2, 3, 4, 5, 6, 7, 8, 9].filter(self => !(self % 3 === 0));

有同窗確定納悶爲何不用forEach作呢?這是由於forEach不像for循環能重置i同樣重置index,其次不像filter能return數據,對於forEach使用更多細節能夠閱讀博主這篇文章 forEach參數詳解,forEach與for循環區別 。那麼關於數組過濾就說到這裏了。

貳 ❀ 陸 判斷數據是否包含某元素

同爲高頻操做,不少同窗習慣使用for或者forEach用來作此操做,其實相比之下,find與some方法更爲實現,先看find:

var result = ['echo', '聽風是風', '時間跳躍', '聽風是風'].find((self) => {
    console.log(1);//執行2次
    return self === '聽風是風'
});
console.log(result); //聽風是風

再看some方法:

var result = ['echo', '聽風是風', '時間跳躍'].some((self) => {
    console.log(1);//執行2次
    return self === '聽風是風'
});
console.log(result); //true

find方法返回第一個符合條件的目標元素,並跳出循環,而some只要找到有一個符合條件則返回布爾值true。二者都自帶跳出循環機制,相比for循環使用break以及forEach沒法break更加方便,特別是some的返回結果更利於後面的條件判斷邏輯。

另外ES6數組新增了簡單粗暴的includes方法,能直接用於判斷數組是否包含某元素,最大亮點就是能判斷是否包含NaN,畢竟你們都知道NaN是惟一不等於本身的特殊存在。

[1,2,3,NaN].includes(NaN);//true

includes方法徹底不兼容IE,這裏只是順帶一提,實際開發中還得謹慎使用。

貳 ❀ 柒 數組求並集、交集、差集

在說實現以前,咱們簡單複習數學中關於並集,交集與差集的概念。

假設如今有數組A [1,2,3]與數組B [3,4,5],由於3在兩個數組中均有出現,因此3是數組AB的交集。

那麼對應的數字1,2只在A中存在,4,5只在B中出現,因此1,2,3,4屬於AB的共同差集。

而並集則是指分別出如今AB中的全部數字,但不記重複,因此是1,2,3,4,5,注意只有一個3。

在瞭解基本概念後,咱們先說說如何作到求並集;聰明的同窗立刻就想到了並集等於數組合並加去重:

//ES6 求並集
function union(a, b) {
    return a.concat(b).filter((self, index, arr) => index === arr.indexOf(self));
};
console.log(union([1, 2, 3], [3, 4, 5])); //[1,2,3,4,5]

固然使用存在兼容性的ES6會更簡單:

//ES6 求並集
function union(a, b) {
    return Array.from(new Set([...a, ...b]));
};
console.log(union([1, 2, 3], [3, 4, 5])); //[1,2,3,4,5]

咱們再來講說數組求交集,即元素同時存在兩個數組中,由於太困了,這裏我偷個懶使用了includes方法:

function intersect(a, b) {
    return a.filter(self => {
        return b.includes(self);
    });
};
console.log(intersect([1, 2, 3], [3, 4, 5]));//[3]

差集就好說了,在上方代碼中includes前加個!便可,這裏作個演示只求b數組的差集:

function difference (a, b) {
    return a.filter(self => {
        return !(b.includes(self));
    });
};
console.log(difference ([1, 2, 3], [3, 4, 5])); //[1, 2]

叄 ❀ 總

那麼到這裏,咱們藉着彙總數組常見操做的契機,複習了數組常見API與部分容易忽略的知識。對於數組去重,降維,排序等操做都至少給出了一種解決思路。如有對於文中實現有更好的建議或疑問,也歡迎你們留言。我會在第一時間回覆。另外,撕帶油的遊戲必定要當心當心再當心,否則就會像我這樣毀掉一件衣服。

那麼本文到這裏就結束了,我是真的好睏好睏,我還沒買到回家的票!!!!含淚睡覺。

相關文章
相關標籤/搜索