回顧 ES5新增陣列方法 與實現

跨越銀河Galaxy+發表於2020-02-01

2020 春節疫情嚴重, 宅在家的時間, 就複習一下 ES5 的陣列迭代方法吧. 作為一名合格的前端(API)工程師, 理應熟練掌握 ES5 中新增的幾種陣列迭代方法與其蘊含的思想, 溫故而知新,也是一個良好的學習態度嘛.

新增方法

  • forEach
  • map
  • filter
  • some/every
  • reduce/reduceRight
  • indexOf/lastIndexOf

forEach

對陣列的每個元素執行一次回撥函式

const arr = [1, 2, 3, 4];
arr.forEach((currentItem, index, array) =>
  console.log(currentItem * currentItem)
);
// 1
// 4
// 9
// 16
複製程式碼

由於 forEach 沒有返回值, 只是單純遍歷陣列, 所以它往往只是用來作簡單的陣列迭代. 那與 for 迴圈的區別在哪呢, for 迴圈作為 js 最原始的迭代方法, 邏輯程式碼的編寫會更自由更方便, 而 forEach 把焦點聚焦在了回撥函式中, 會讓我們更關注業務而不是遍歷物件/迴圈次數, 作為 for 迴圈的特殊版本, forEach 並不能 使用 break/continue 退出迴圈, 也不支援 async/await, 本文對此不作深入研究, 具體瞭解請看 當 async/await 遇上 forEach.下面給出 基於 for 迴圈實現的 forEach:

Array.prototype.forEach = function(callback) {
  if (!callback) throw new Error("undefined is not a function");
  if (typeof callback !== "function")
    throw new Error("callback is not a function");
  for (let i = 0; i < this.length; i++) {
    callback(this[i], i, this);
  }
};
複製程式碼

如果不需要使用 break/continue 退出迴圈, 或是在 forEach 中作非同步請求, 與 for 迴圈 的區別並不大, 但既然有了新的 API, 當然更推薦使用 forEach. forEach 的入參是一個函式, 本質是一個高階函式, 事實上 ES5 提供的 幾個陣列新 API 全是高階函式的形式, 想了解高階函式的知識, 請移步 高階函式,你怎麼那麼漂亮呢!.

map

遍歷提供的陣列, 並返回傳入函式作用於原陣列每項的新陣列

const arr1 = [1, 2, 3, 4];
const resultArray = arr1.map(
  (currentItem, index, array) => currentItem * currentItem
);
console.log(resultArray);
// [1, 4, 9, 16]
複製程式碼

請注意將 forEach 與 map 進行聯絡與對比, 兩者均接受一個函式作為入參, 函式的入參也都是: (當前遍歷項, 當前索引值, 陣列自身), forEach 返回 undefined, 而 map 返回經過函式運算後的新陣列, 所以此時區別出來了, 當我們需要使用返回後的新陣列時, 就應該使用 map 而不是 forEach, 而 map 的出現, 也恰恰體現出了函數語言程式設計的思想, 如果每次給 map 運算的 陣列都是相同的, 那它一定給你永遠一樣的答案, 想了解函數語言程式設計, 請跳轉到 函數語言程式設計入門教程, 實現一個 map

Array.prototype.map = function(callback) {
  if (!callback) throw new Error("undefined is not a function");
  if (typeof callback !== "function")
    throw new Error("callback is not a function");
  const ret = [];
  for (let i = 0; i < this.length; i++) {
    const item = callback(this[i], i, this);
    ret.push(item);
  }
  return ret;
};
複製程式碼

filter

遍歷提供的陣列, 並返回 符合傳入函式運算結果為 true 的項

const arr2 = [1, 2, 3, 4, 5, 99, 1000, 9999];
// 找出值大於或等於 1000 的項
const filterResult = arr2.filter((item, index, array) => item >= 1000);
console.log(filterResult);
// [9999]

// 找出索引位置是偶數的項
const filterResult2 = arr2.filter((item, index, array) => index % 2 === 0);
// [1, 3, 5, 1000]
複製程式碼

fitler 運用的關鍵在於[表示式的運算], 表示式運算後的值為 [true] 的項才會被返回, 而 js 有一項神祕技能 -- 隱式轉換, 所以請務必掌握 [隱式轉換], 這個在 js 中神祕而又古老的特性, 你所忽略的 js 隱式轉換.

Array.prototype.filter = function(callback) {
  if (!callback) throw new Error("undefined is not a function");
  if (typeof callback !== "function")
    throw new Error("callback is not a function");
  const ret = [];
  for (let i = 0; i < this.length; i++) {
    const item = callback(this[i], i, this);
    if (Boolean(item)) {
      ret.push(this[i]);
    }
  }
  return ret;
};
複製程式碼

some/every

some 與 every 可以聯絡對比起來理解, some 與 every 都是在遍歷給定陣列後, 返回一個布林值, 其值由傳入的函式運算後得出, some 要求 [至少有 1 個]元素經過傳入函式運算為 true, 而 every 則要求 [全部] 元素經過偉入函式運算為 true.

  • some
const arr3 = [1, 2, 3, 4, 5, 6];
// 測試 至少有 1 個元素 符合 >= 6 這個條件
const someTestResult1 = arr3.some(item => item >= 6);
console.log(someTestResult1);
// true

// 測試 至少有 1 個元素 符合 >= 7 這個條件
const someTestResult2 = arr3.some(item => item >= 7);
console.log(someTestResult2);
// false
複製程式碼
  • every
const arr4 = [1, 2, 3, 4, 99, 100, 9999];
// 測試 全部元素 符合 > 0 這個條件
const everyTestResult1 = arr4.every(item => item > 0);
console.log(everyTestResult1);
// true

// 測試 全部元素 符合 <100 這個條件
const everyTestResult2 = arr4.every(item => item < 100);
console.log(everyTestResult2);
// false
複製程式碼

some 與 every 的出現, 讓我們能用更簡短的程式碼實現需求, 最典型的莫過於 在購物車頁面 判斷使用者是否選中了全部商品, const isSelectedAll = goodsList.every(goods => goos.isSelected) 就搞定, 而結算按鈕必須要至少選中一個商品才可以點選, const selectedAtLeastOne = goodsList.some(goods => goos.isSelected) 就搞定, 再也不用 for 來 for 去.

some 的實現:

Array.prototype.some = function(callback) {
  if (!callback) throw new Error("undefined is not a function");
  if (typeof callback !== "function")
    throw new Error("callback is not a function");
  const ret = true;
  for (let i = 0; i < this.length; i++) {
    const item = callback(this[i], i, this);
    if (Boolean(item)) break;
  }
  return ret;
};
複製程式碼

every 的實現:

Array.prototype.every = function(callback) {
  if (!callback) throw new Error("undefined is not a function");
  if (typeof callback !== "function")
    throw new Error("callback is not a function");
  let ret = false;
  for (let i = 0; i < this.length; i++) {
    const item = callback(this[i], i, this);
    if (!Boolean(item)) break;
    ret = true;
  }
  return ret;
};
複製程式碼

reduce/reduceRight

對陣列中的每個元素執行一個給定的 reducer 函式(升序執行),將其結果彙總為單個返回值

const arr5 = [1, 110, 111, 444];
// 求和
const sumary = arr5.reduce(
  (accumulator, currentItem) => accumulator + currentItem
); // accumulator: 累加值 currentItem: 當前值
console.log(sumary);
// 666
複製程式碼

請注意 reduce 與其它幾個同胞兄弟的不同是在於, 在 currentItem 前還多了個 accumulator (累加值), 累計器累計回撥的返回值, 是上一次呼叫回撥時返回的累積值,或 initialValue (初始值). 完整入參是 .reduce((accumulator, currentItem, index, array) => { ... }, initialValue), 鑑於 reduce 的強大特性, 很多業務場景都可以用到, 所以更多實用指南, 請參考 Array.prototype.reduce 實用指南, 下面給出實現:

Array.prototype.reduce = function(callback, initalValue) {
  if (!callback) throw new Error("undefined is not a function");
  if (typeof callback !== "function")
    throw new Error("callback is not a function");
  let __init = initalValue,
    i = 0;
  if (__init === undefined) {
    __init = this[0];
    i = 1;
  }
  for (i; i < this.length; i++) {
    __init = callback(__init, this[i], i, this);
  }
  return __init;
};
複製程式碼

indexOf/lastIndexOf

返回找到的 第一個/最後一個 元素的索引, 找不到時返回 -1

const arr6 = ['Jalon', 'Galaxy', 'Mega', 'Star', 'Earth'];
const indexResult = arr6.indexOf('Galaxy');
console.log(indexResult);
// 1
複製程式碼

這組 方法沒什麼好說的, 要不, 你也動手實現一下?

Array.prototype.indexOf = function(callback) {
  if (!callback) throw new Error("undefined is not a function");
  if (typeof callback !== "function")
    throw new Error("callback is not a function");
  let _index = -1;
  for (let i = 0; i < this.length; i++) {
    ...你的程式碼在這兒
  }
  return _index;
};
複製程式碼

小結

以上就是 ES5 最經典的陣列遍歷方法了, 事實上在我們的開發中, 這些新增的方法不僅能讓我們能更快更優雅地編寫業務程式碼, 也把我們引向了函數語言程式設計的領域, 在程式碼簡潔的同時也保持極強的可讀性, 可謂一石二鳥. 熟悉的讀者朋友可以點左上角的小手指點個贊, 不熟悉的朋友就再琢磨琢磨, 畢竟前端(API)工程師要做好, 也不容易. 互勉, 也順便祝願疫情快點好轉,讓中國順利度過這場劫難, thank you~~~

相關文章