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~~~