JS陣列去重!!!一篇不怎麼靠譜的"深度"水文

CRPER發表於2017-05-11

前言

陣列去重,這是一個老梗了...今天我又拿出來說了...
我們在考慮全面一點的情況下,陣列去重的實現,比如針對NaN,undefined,{};
這其中涉及的知識點挺多,不信跟著走一波;
這裡不考慮瀏覽器相容性這些破問題,因為涉及ES5&6


基礎版-只包含一些可以直接比較的數值

測試陣列

[1,1,'','','e','e',true,'true',true,false,false,'false',undefined,'undefined',undefined,null,'null',null]

  • [ES5]萬能的for方法
function uniqueUseFor(array) {
  var temp = []; //一個臨時陣列

  //遍歷當前陣列
  for (var i = 0, j = array.length; i < j; i++) {
    //很直白,新陣列內判斷是否有這個值,沒有的情況下,就推入該新陣列
    temp.indexOf(array[i]) === -1 ? temp.push(array[i]) : '';
  }
  return temp;
}

// result: [1, "", "e", true, "true", false, "false", undefined, "undefined", "null", null]複製程式碼
  • [ES5]內建的forEach方法
function uniqueUseForEach(array) {
  // 傳入值必須存在,且長度小於等於1的時候直接返回陣列
  if (array && array.length <= 1) {
    return array;
  } else {
    var temp = []; //一個臨時陣列
    //遍歷當前陣列
    array.forEach(function (value, index) {
      temp.indexOf(value) == -1 ? temp.push(value) : '';
    })
    return temp;
  }
}
// result: [1, "", "e", true, "true", false, "false", undefined, "undefined", "null", null]複製程式碼
  • [ES6]內建的for-of方法
function uniqueUseForOf(array) {
  const temp = []; //一個臨時陣列
  // 傳入值必須存在,且長度小於等於1的時候直接返回陣列
  if (array && array.length <= 1) {
    return array;
  } else {
    //遍歷當前陣列
    for (let x of array) {
      temp.indexOf(x) === -1 ? temp.push(x) : '';
    }

  }
  return temp;
}
// result: [1, "", "e", true, "true", false, "false", undefined, "undefined", "null", null]複製程式碼

進階版- 包含NaN,undefined,null

測試陣列

[1,1,'true',true,true,5,'F',false, undefined, null,null,undefined, NaN, 0, 1, 'a', 'a', NaN,'NaN']

知識點

  • NaN有兩中通用判定方法和陣列中一種判定方法:

    • 一個是絕對不全等於(===)自身
    • 一個是ES6isNaN()
    • 陣列原型鏈上的Array.prototype.includes()
  • [ES5]: 不等特性,需要藉助佔位符

function uniqueUseNotAllEqual(array) {

  var temp = [], //一個臨時陣列
      mark = true; // 標識位

  //遍歷當前陣列
  for (var i = 0, j = array.length; i < j; i++) {
    // 標識位的作用就是用來判斷是否存在NaN,第一次找到保留到新陣列中
    // 然後標識位置改為false是為了再次找到的時候不推入陣列
    if (array[i] !== array[i]) {
      // 這裡的不等特性,也可以用isNaN判斷[ES6]
      mark && temp.indexOf(array[i]) == -1 ? temp.push(array[i]) : '';
      mark = false;

    } else
      temp.indexOf(array[i]) == -1 ? temp.push(array[i]) : '';

  }
  return temp;
}

// result: [1, "true", true, 5, "F", false, undefined, null, NaN, 0, "a", "NaN"]複製程式碼
  • [ES6]內建Array.prototype.includes()大法
function uniqueCompareUseIncludes(array) {
  // 傳入值必須存在,且長度小於等於1的時候直接返回陣列
  if (array && array.length <= 1) {
    return array;
  } else {
    let temp = []; //一個臨時陣列
    //遍歷當前陣列
    for (let x of array) {
      // includes() 方法用來判斷當前陣列是否包含某指定的值,如果是,則返回 true,否則返回 false。
      temp.includes(x) ? '': temp.push(x) ;
    }
    return temp;
  }
}

// result: [1, "true", true, 5, "F", false, undefined, null, NaN, 0, "a", "NaN"]複製程式碼
  • [ES6] Array.from或擴充運算子[...]結合Set大法

知識點

  • Set的值具有唯一性,內部會自動===比較,是可迭代物件(iterable),有點特殊的是NaN這貨雖然有不全等的特性,在Set裡面認為是相同的,所以只能有一個
  • Array.from...可以把類似陣列【nodelist or arguments】這類可迭代的物件中轉為一個標準的陣列
// Array.from + Set的方法
Array.from(new Set([1,1,'true',true,true,5,'F',false, undefined, null,null,undefined, NaN, 0, 1, 'a', 'a', NaN,'NaN']))
// resule: [1, "true", true, 5, "F", false, undefined, null, NaN, 0, "a", "NaN"]


// ... + Set的方法
[...new Set([1,1,'true',true,true,5,'F',false, undefined, null,null,undefined, NaN, 0, 1, 'a', 'a', NaN,'NaN'])]
//result: [1, "true", true, 5, "F", false, undefined, null, NaN, 0, "a", "NaN"]複製程式碼

高階版- 包含{},NaN,undefined,null

測試陣列

[1,1,'true',true,true,5,'F',false, undefined, null,null,undefined, NaN,{},{},'{}', 0, 1, 'a', 'a', NaN]

知識點

  • {}的比較真心不好做,有殘缺性的比較可以這樣寫 JSON.stringify({}) == '{}'
  • 一個比較完美的方案是藉助for in結合原型鏈的toString來判斷

  • [ES5]for-in + call + for方案

function uniqueUseForIn(array) {
  var temp = [];
  var emptyObjectMark = true; // 標識位
  var NaNObjectMark = true; // 標識位
  // 判斷空物件,這塊判斷折騰了許久
  function isEmptyObject(object) {
    if (Object.prototype.toString.call(object) === "[object Object]") {
      for (var i in object) {
        // 存在屬性或方法,則不是空物件
        return false
      }
      return true;
    } else {
      return false;
    }
  }

  // 傳入值必須存在,且長度小於等於1的時候直接返回陣列
  if (array && array.length <= 1) {
    return array;
  } else {
    //遍歷當前陣列
    for (var i = 0, j = array.length; i < j; i++) {
      // 標識位的作用就是用來判斷是否存在NaN和空物件,第一次找到保留到新陣列中
      // 然後標識位置改為false是為了再次找到的時候不推入陣列
      if (isEmptyObject(array[i])) {
        emptyObjectMark && temp.indexOf(array[i]) == -1 ? temp.push(array[i]) : '';
        emptyObjectMark = false;
      } else if (array[i] !== array[i]) {
        NaNObjectMark && temp.indexOf(array[i]) == -1 ? temp.push(array[i]) : '';
        NaNObjectMark = false;
      } else
        temp.indexOf(array[i]) == -1 ? temp.push(array[i]) : '';

    }
    return temp;

  }
}

// result:[1, "true", true, 5, "F", false, undefined, null, NaN, Object, "{}", 0, "a"]複製程式碼

擴充版:多維陣列扁平化再去重;

回應:

留言板的小夥伴說去重深度不夠。。2017/5/12

測試陣列

[1, 1, [['true', true, true, [5, 'F'], false], undefined], null, null, [undefined, NaN, {}], {}, '{}', 0,1, 'a','a', NaN]

知識點

  • 陣列扁平化用了遞迴實現
  • 程式碼也考慮了預設引數,防止不傳遞引數的時候報錯
  • [ES5]for-in + call + for + 遞迴扁平化方案
function uniqueArrayWithFlattern(array) {
  var _array = array || []; // 儲存傳參
  // 判斷空物件,這塊判斷折騰了許久
  function isEmptyObject(object) {
    if (Object.prototype.toString.call(object) === "[object Object]") {
      for (var i in object) {
        // 存在屬性或方法,則不是空物件
        return false
      }
      return true;
    } else {
      return false;
    }
  }



  // 遍歷查詢判斷,扁平化陣列
  function forArrayFlattern(arr) {

    for (var m = 0, n = arr.length; m < n; m++) {
      if (Array.isArray(arr[m])) {
        var _tempSpliceArr = _array.splice(m, 1)[0];
        _array = _array.concat(_tempSpliceArr);
        return forArrayFlattern(_array);
      }
    }
    return uniqueArr(_array);
  }


  // 傳入值必須存在,且長度小於等於1的時候直接返回陣列
  if (_array && _array.length <= 1) {
    return _array
  } else {
    if (Array.isArray(_array)) {
      return forArrayFlattern(_array)
    } else {
      return _array;
    }
  }

  // 陣列去重
  function uniqueArr(_array) {
    var temp = [];
    var emptyObjectMark = true; // 標識位
    var NaNObjectMark = true; // 標識位
    console.log(_array.length)
    //遍歷當前陣列
    for (var a = 0, b = _array.length; a < b; a++) {
      // 標識位的作用就是用來判斷是否存在NaN和空物件,第一次找到保留到新陣列中
      // 然後標識位置改為false是為了再次找到的時候不推入陣列

      console.log(_array[a]);
      if (isEmptyObject(_array[a])) {
        emptyObjectMark && temp.indexOf(_array[a]) == -1 ? temp.push(_array[a]) : '';
        emptyObjectMark = false;
      } else if (_array[a] !== _array[a]) {
        NaNObjectMark && temp.indexOf(_array[a]) == -1 ? temp.push(_array[a]) : '';
        NaNObjectMark = false;
      } else {
        temp.indexOf(_array[a]) === -1 ? temp.push(_array[a]) : '';
      }
    }
    console.log(temp);
    return temp;
  }
}

// result:[1, null, Object, "{}", 0, "a", NaN, undefined, "true", true, false, 5, "F"]
// 用ES6來寫的話,應該程式碼量可以稍微再精簡些複製程式碼

總結

相信各位小夥伴把這個弄懂了之後,各種面試中的花樣陣列去重要求對你來說都不是很坑了;

相關文章