【跟著大佬學JavaScript】之陣列去重(結果對比)

2022-07-21 06:00:22

前言

陣列去重在面試和工作中都是比較容易見到的問題。

這篇文章主要是來測試多個方法,對下面這個陣列的去重結果進行分析討論。如果有不對的地方,還請大家指出。

 const arr = [ 1, 1, "1", "1", 0, 0, "0", "0", true, false, "true", "false", "a", "A", undefined, undefined, "undefined", null, null, 'null', NaN, NaN, +0, -0, new String("1"), new String("1"), Symbol(1), Symbol(1), {}, {}, /a/, /a/, [], [] ];

特殊型別

console.log(1 == "1"); // true
console.log(1 === "1"); // false

console.log(0 == "0"); // true
console.log(0 === "0"); // false

console.log(0 == +0); // true
console.log(0 === +0); // true

console.log(0 == -0); // true
console.log(0 === -0); // true

console.log(+0 == -0); // true
console.log(+0 === -0); // true

console.log(0 == false); // true
console.log(0 === false); // false

console.log(0 == undefined); // false
console.log(0 === undefined); // false

console.log(0 == null); // false
console.log(0 === null); // false

console.log(1 == true); // true
console.log(1 === true); // false

console.log(undefined == null); // true
console.log(undefined === null); // false

console.log(NaN == NaN); // false
console.log(NaN === NaN); // false

console.log(new String("1") == new String("1")); // false
console.log(new String("1") === new String("1")); // false
Object.prototype.toString.call(new String('1')) // '[object String]'


console.log(/a/ == /a/); // false
console.log(/a/ === /a/); // false
Object.prototype.toString.call(/a/); //'[object RegExp]'


console.log(Symbol(1) == Symbol(1)); // false
console.log(Symbol(1) === Symbol(1)); // false

console.log({} == {}); // false
console.log({} === {}); // false

console.log([] == []); // false
console.log([] === []); // false

接下來,我們看看下面多個去重方法,對以上特殊型別的去重效果。

程式碼一(暴力解法)

// 暴力解法一

function unique(array) {
    if (!Array.isArray(array)) {
      console.log("type error!");
      return;
    }
    const res = [array[0]];
    let arrLen = array.length;
    let resLen = res.length;
    
    for (let i = 0; i < arrLen; i++) {
      let flag = true;
      for (let j = 0; j < resLen; j++) {
        if (array[i] === res[j]) {
          flag = false;
          break;
        }
      }
      if (flag) {
        res.push(array[i]);
        resLen = res.length;
      }
    }
    return res;
}
// [1, '1', 0, '0', true, false, 'true', 'false', 'a', 'A', undefined, 'undefined', null, 'null', NaN, NaN, String {'1'}, String {'1'}, Symbol(1), Symbol(1), {}, {}, /a/, /a/, [], []]

輸出:

[1, '1', 0, '0', true, false, 'true', 'false', 'a', 'A', undefined, 'undefined', null, 'null', NaN, NaN, String {'1'}, String {'1'}, Symbol(1), Symbol(1), {}, {}, /a/, /a/, [], []]

輸出結果說明:

  1. 去重+0-00
  2. NaN不去重
  3. 物件new String("1")/a/{}不去重
  4. 陣列[]不去重
  5. Symbol(1)不去重

暴力解法,簡單易理解,相容性好。去重結果如上所示。

程式碼二(ES6)

// ES6 Array.from + Set 方法一
function unique(array) {
    if (!Array.isArray(array)) {
      console.log('type error!')
      return
    }
    return Array.from(new Set(array))
}

// ES6 點運算 + Set 方法二
function unique1(array) {
    if (!Array.isArray(array)) {
      console.log('type error!')
      return
    }
    return [...new Set(arr)]
}

// ES6 箭頭函數 + 點運算 + Set 方法三
const unique2 = (array) => {
    if (!Array.isArray(array)) {
      console.log('type error!')
      return
    }
    return [...new Set(arr)]
}

// ES6 Map + ES5 filter  方法四
function unique3(array) {
    if (!Array.isArray(array)) {
      console.log('type error!')
      return
    }
    const seen = new Map()
    return array.filter((a) => !seen.has(a) && seen.set(a, 1))
}

輸出:

[1, '1', 0, '0', true, false, 'true', 'false', 'a', 'A', undefined, 'undefined', null, 'null', NaN, String {'1'}, String {'1'}, Symbol(1), Symbol(1), {}, {}, /a/, /a/, [], []]

輸出結果說明:

  1. 去重+0-00
  2. 去重NaN
  3. 物件new String("1")/a/{}不去重
  4. 陣列[]不去重
  5. Symbol(1)不去重

程式碼三(indexOf + forEach)

利用indexOf檢測元素在新陣列是否存在

// indexOf + forEach 利用indexOf檢測元素在新陣列是否存在
function unique(array) {
    if (!Array.isArray(array)) {
        console.log('type error!')
        return
    }
    const newArr = [];
    array.forEach((el) => {
      if (newArr.indexOf(el) === -1) {
        newArr.push(el);
      }
    });
    return newArr;
}

輸出:

[1, '1', 0, '0', true, false, 'true', 'false', 'a', 'A', undefined, 'undefined', null, 'null', NaN, NaN, String {'1'}, String {'1'}, Symbol(1), Symbol(1), {}, {}, /a/, /a/, [], []]

輸出結果說明:

  1. 去重+0-00
  2. NaN不去重
  3. 物件new String("1")/a/{}不去重
  4. 陣列[]不去重
  5. Symbol(1)不去重

程式碼四(indexOf + filter)

利用indexOf檢測元素在陣列中第一次出現的位置是否和元素現在的位置相等

// indexOf + forEach 利用indexOf檢測元素在新陣列是否存在
function unique(array) {
    if (!Array.isArray(array)) {
        console.log('type error!')
        return
    }
    return array.filter((item, index) => {
        return array.indexOf(item) === index;
    });
}

console.log([NaN].indexOf(NaN)); // -1

輸出:

[1, '1', 0, '0', true, false, 'true', 'false', 'a', 'A', undefined, 'undefined', null, 'null', String {'1'}, String {'1'}, Symbol(1), Symbol(1), {}, {}, /a/, /a/, [], []]

輸出結果說明:

  1. 去重+0-00
  2. 兩個NaN都會被刪除
  3. 物件new String("1")/a/{}不去重
  4. 陣列[]不去重
  5. Symbol(1)不去重

重點:

console.log([NaN].indexOf(NaN)); // -1

程式碼五(sort排序,不支援Symbol)

sort()方法主要是用於對陣列進行排序,預設情況下該方法是將陣列元素轉換成字串,然後按照ASC碼進行排序

// sort()方法不支援Symbol,Symbol不支援轉換成字串
function unique(array) {
    if (!Array.isArray(array)) {
      console.log("type error!");
      return;
    }
    const sortArr = array.sort();
    const newArr = [];
    sortArr.forEach((el, i) => {
      if (sortArr[i] !== sortArr[i - 1]) {
        newArr.push(el);
      }
    });
    return newArr;
}

輸出:

[[], [], /a/, /a/, 0, "0", 0, 1, "1", String {'1'}, String {'1'}, "A", NaN, NaN, {}, {}, "a", false, "false", null, "null", true, "true", "undefined", undefined]

輸出結果說明:

  1. +0-00"0"位置不同會導致去重不了
  2. NaN不去重
  3. 物件new String("1")/a/{}不去重
  4. 陣列[]不去重
  5. sort()方法不支援處理含有Symbol的陣列

程式碼六(includes)

利用includes()方法檢查新陣列是否包含原陣列的每一項

// 利用includes()方法檢查新陣列是否包含原陣列的每一項
function unique(array) {
    if (!Array.isArray(array)) {
      console.log("type error!");
      return;
    }
    
    const newArr = [];
    array.forEach((el) => {
      newArr.includes(el) ? newArr : newArr.push(el);
    });
    return newArr;
}

輸出:

[1, '1', 0, '0', true, false, 'true', 'false', 'a', 'A', undefined, 'undefined', null, 'null', NaN, String {'1'}, String {'1'}, Symbol(1), Symbol(1), {}, {}, /a/, /a/, [], []]

輸出結果說明:

  1. 去重+0-00
  2. 去重NaN
  3. 物件new String("1")/a/{}不去重
  4. 陣列[]不去重
  5. Symbol不去重

程式碼七(includes+reduce)

利用includes()方法檢查新陣列是否包含原陣列的每一項

// 利用includes()方法檢查新陣列是否包含原陣列的每一項
function unique(array) {
    if (!Array.isArray(array)) {
      console.log("type error!");
      return;
    }
    
    return array.reduce((pre, cur) => {
      !pre.includes(cur) && pre.push(cur);
      return pre;
    }, []);
}

輸出:

[1, '1', 0, '0', true, false, 'true', 'false', 'a', 'A', undefined, 'undefined', null, 'null', NaN, String {'1'}, String {'1'}, Symbol(1), Symbol(1), {}, {}, /a/, /a/, [], []]

輸出結果說明:

  1. 去重+0-00
  2. 去重NaN
  3. 物件new String("1")/a/{}不去重
  4. 陣列[]不去重
  5. Symbol不去重

程式碼八(物件key)

利用了物件的key不可以重複的特性來進行去重

// 利用了物件的key不可以重複的特性來進行去重
function unique(array) {
    if (!Array.isArray(array)) {
      console.log("type error!");
      return;
    }
    
    const obj = {};
    const newArr = [];
    array.forEach((val) => {
      if (!obj[typeof val + JSON.stringify(val)]) {
        // 將物件序列化之後作為key來使用
        obj[typeof val + JSON.stringify(val)] = 1;
        newArr.push(val);
      }
    });
    return newArr;
}

輸出:

[1, '1', 0, '0', true, false, 'true', 'false', 'a', 'A', undefined, 'undefined', null, 'null', NaN, String {'1'}, Symbol(1), {}, []]

輸出結果說明:

  1. 去重+0-00
  2. 去重NaN
  3. 去重物件new String("1"){};兩個/a/全部被刪除了
  4. 去重陣列[]
  5. 去重Symbol

將不該去重的Symbol去重了;將兩個/a/全部刪除了

總結

方法 結果 說明
for迴圈暴力解法 [1, '1', 0, '0', true, false, 'true', 'false', 'a', 'A', undefined, 'undefined', null, 'null', NaN, NaN, String {'1'}, String {'1'}, Symbol(1), Symbol(1), {}, {}, /a/, /a/, [], []] 1.去重+0、-0、0; 2.NaN不去重;3.物件new String("1")、/a/、{}不去重;4.陣列[]不去重;5.Symbol(1)不去重;
ES6解法 [1, '1', 0, '0', true, false, 'true', 'false', 'a', 'A', undefined, 'undefined', null, 'null', NaN, String {'1'}, String {'1'}, Symbol(1), Symbol(1), {}, {}, /a/, /a/, [], [] 1.去重+0、-0、0; 2.去重NaN;3.物件new String("1")、/a/、{}不去重;4.陣列[]不去重;5.Symbol(1)不去重;
indexOf + forEach [1, '1', 0, '0', true, false, 'true', 'false', 'a', 'A', undefined, 'undefined', null, 'null', NaN, NaN, String {'1'}, String {'1'}, Symbol(1), Symbol(1), {}, {}, /a/, /a/, [], []] 1.去重+0、-0、0; 2.NaN不去重;3.物件new String("1")、/a/、{}不去重;4.陣列[]不去重;5.Symbol(1)不去重;
indexOf + filter [1, '1', 0, '0', true, false, 'true', 'false', 'a', 'A', undefined, 'undefined', null, 'null', String {'1'}, String {'1'}, Symbol(1), Symbol(1), {}, {}, /a/, /a/, [], []] 1.去重+0、-0、0; 2.兩個NaN都會被刪除;3.物件new String("1")、/a/、{}不去重;4.陣列[]不去重;5.Symbol(1)不去重;
sort排序,不支援Symbol [[], [], /a/, /a/, 0, "0", 0, 1, "1", String {'1'}, String {'1'}, "A", NaN, NaN, {}, {}, "a", false, "false", null, "null", true, "true", "undefined", undefined] 1.+0、-0、0、"0"位置不同會導致去重不了 2.NaN不去重;3.物件new String("1")、/a/、{}不去重;4.陣列[]不去重;5.sort()方法不支援處理含有Symbol的陣列;
includes [1, '1', 0, '0', true, false, 'true', 'false', 'a', 'A', undefined, 'undefined', null, 'null', NaN, String {'1'}, String {'1'}, Symbol(1), Symbol(1), {}, {}, /a/, /a/, [], []] 1.去重+0、-0、0; 2.去重NaN;3.物件new String("1")、/a/、{}不去重;4.陣列[]不去重;5.Symbol(1)不去重;
includes+reduce [1, '1', 0, '0', true, false, 'true', 'false', 'a', 'A', undefined, 'undefined', null, 'null', NaN, String {'1'}, String {'1'}, Symbol(1), Symbol(1), {}, {}, /a/, /a/, [], []] 1.去重+0、-0、0; 2.去重NaN;3.物件new String("1")、/a/、{}不去重;4.陣列[]不去重;5.Symbol(1)不去重;
物件key [1, '1', 0, '0', true, false, 'true', 'false', 'a', 'A', undefined, 'undefined', null, 'null', NaN, String {'1'}, Symbol(1), {}, []] 1.去重+0、-0、0; 2.去重NaN;3.去重物件new String("1")、{};兩個/a/全部被刪除了;4.去重陣列[];5.去重Symbol

上面只是簡單結果的去重總結,具體的去重選擇還需要根據我們業務場景來選擇去重方法。

演示地址

可以去Github倉庫檢視演示程式碼

跟著大佬學系列

主要是日常對每個進階知識點的摸透,跟著大佬一起去深入瞭解JavaScript的語言藝術。

後續會一直更新,希望各位看官不要吝嗇手中的贊。

❤️ 感謝各位的支援!!!

❤️ 如果有錯誤或者不嚴謹的地方,請務必給予指正,十分感謝!!!

❤️ 喜歡或者有所啟發,歡迎 star!!!

參考

原文地址

【跟著大佬學JavaScript】之陣列去重(結果對比)