摘要: 通過代碼掌握數組方法。 原文: "通過實現25個數組方法來理解及高效使用數組方法(長文,建議收藏)" 譯者:前端小智 "Fundebug" 經授權轉載,版權歸原作者所有。 要在給定數組上使用方法,只需要通過 即可,這些方法都定義在 對象上。在這裡,咱們先不使用這些相,反,咱們將從簡單的方法開 ...
摘要: 通過代碼掌握數組方法。
- 原文:通過實現25個數組方法來理解及高效使用數組方法(長文,建議收藏)
- 譯者:前端小智
Fundebug經授權轉載,版權歸原作者所有。
要在給定數組上使用方法,只需要通過[].方法名
即可,這些方法都定義在 Array.prototype
對象上。在這裡,咱們先不使用這些相,反,咱們將從簡單的方法開始定義自己的版本,併在這些版本的基礎上進行構建。
沒有比把東西拆開再重新組裝起來更好的學習方法了。註意,當咱們的實現自己的方法時,不要覆蓋現有的方法,因為有的庫需要它們,並且這樣也方便比較咱們自己的方法與原始方法的差異。
所以不要這樣命名咱們自定義的方法:
Array.prototype.map = function map() {
// implementation
};
最好這樣命名:
function map(array) {
// implementation
}
咱們也可以通過使用class
關鍵字並擴展Array
構造函數來實現咱們的方法,如下所示:
class OwnArray extends Array {
public constructor(...args) {
super(...args);
}
public map() {
// implementation
return this;
}
}
唯一的區別是,我們不使用數組參數,而是使用this
關鍵字。
但是,我覺得 class 方式帶來不必要的混亂,所以咱們採用第一種方法。
有了這個,咱們先從實現最簡單的方法 forEach
開始!
集合類
.forEach
Array.prototype.forEach
方法對數組的每個元素執行一次提供的函數,而且不會改變原數組。
[1, 2, 3, 4, 5].forEach(value => console.log(value));
實現
function forEach(array, callback) {
const { length } = array;
for (let index = 0; index < length; index += 1) {
const value = array[index];
callback(value, index, array)
}
}
咱們遍曆數組併為每個元素執行回調。這裡需要註意的一點是,該方法沒有返回什麼,所以預設返回undefined
。
方法漣
使用數組方法的好處是可以將操作鏈接在一起。考慮以下代碼:
function getTodosWithCategory(todos, category) {
return todos
.filter(todo => todo.category === category)
.map(todo => normalizeTodo(todo));
}
這種方式,咱們就不必將map
的執行結果保存到變數中,代碼會更簡潔。
不幸的是,forEach
沒有返回原數組,這意味著咱們不能做下麵的事情
// 無法工作
function getTodosWithCategory(todos, category) {
return todos
.filter(todo => todo.category === category)
.forEach((value) => console.log(value))
.map(todo => normalizeTodo(todo));
}
幫助函數 (列印信息)
接著實現一個簡單的函數,它能更好地解釋每個方法的功能:接受什麼作為輸入,返回什麼,以及它是否對數組進行了修改。
function logOperation(operationName, array, callback) {
const input = [...array];
const result = callback(array);
console.log({
operation: operationName,
arrayBefore: input,
arrayAfter: array,
mutates: mutatesArray(input, array), // shallow check
result,
});
}
其中 mutatesArray 方法用來判斷是否更改了原數組,如果有修改剛返回 true
,否則返回 false
。當然大伙有好的想法可以在評論提出呦。
function mutatesArray(firstArray, secondArray) {
if (firstArray.length !== secondArray.length) {
return true;
}
for (let index = 0; index < firstArray.length; index += 1) {
if (firstArray[index] !== secondArray[index]) {
return true;
}
}
return false;
}
然後使用logOperation
來測試咱們前面自己實現的 forEach
方法。
logOperation('forEach', [1, 2, 3, 4, 5], array => forEach(array, value => console.log(value)));
列印結果:
{
operation: 'forEach',
arrayBefore: [ 1, 2, 3, 4, 5 ],
arrayAfter: [ 1, 2, 3, 4, 5 ],
mutates: false,
result: undefined
}
.map
map
方法會給原數組中的每個元素都按順序調用一次 callback
函數。callback
每次執行後的返回值(包括 undefined
)組合起來形成一個新數組。
實現
function map(array, callback) {
const result = [];
const { length } = array;
for (let index = 0; index < length; index +=1) {
const value = array[index];
result[index] = callback(value, index, array);
}
return result;
}
提供給方法的回調函數接受舊值作為參數,並返回一個新值,然後將其保存在新數組中的相同索引下,這裡用變數 result
表示。
這裡需要註意的是,咱們返回了一個新的數組,不修改舊的。
測試
logOperation('map', [1, 2, 3, 4, 5], array => map(array, value => value + 5));
列印結果:
{
operation: 'map',
arrayBefore: [ 1, 2, 3, 4, 5 ],
arrayAfter: [ 1, 2, 3, 4, 5 ],
mutates: false,
result: [ 6, 7, 8, 9, 10 ]
}
.filter
Array.prototype.filter
過濾回調返回為false
的值,每個值都保存在一個新的數組中,然後返回。
[1, 2, 3, 4, 5].filter(number => number >= 3);
// -> [3, 4, 5]
實現
function push(array, ...values) {
const { length: arrayLength } = array;
const { length: valuesLength } = values;
for (let index = 0; index < valuesLength; index += 1) {
array[arrayLength + index] = values[index];
}
return array.length;
}
--------------------------------------------------
function filter(array, callback) {
const result = [];
const { length } = array;
for (let index = 0; index < length; index += 1) {
const value = array[index];
if (callback(value, index, array)) {
push(result, value);
}
}
return result;
}
獲取每個值並檢查所提供的回調函數是否返回true
或false
,然後將該值添加到新創建的數組中,或者適當地丟棄它。
註意,這裡對result
數組使用push
方法,而不是將值保存在傳入數組中放置的相同索引中。這樣,result
就不會因為丟棄的值而有空槽。
測試
logOperation('filter', [1, 2, 3, 4, 5], array => filter(array, value => value >= 2));
運行:
{
operation: 'filter',
arrayBefore: [ 1, 2, 3, 4, 5 ],
arrayAfter: [ 1, 2, 3, 4, 5 ],
mutates: false,
result: [ 2, 3, 4, 5 ]
}
代碼部署後可能存在的BUG沒法實時知道,事後為瞭解決這些BUG,花了大量的時間進行log 調試,這邊順便給大家推薦一個好用的BUG監控工具 [Fundebug][https://www.fundebug.com/?utm_source=xiaozhi]。
.reduce
reduce()
方法接收一個函數作為累加器,數組中的每個值(從左到右)開始縮減,最終計算為一個值。reduce()
方法接受四個參數:初始值(或者上一次回調函數的返回值),當前元素值,當前索引,調用 reduce() 的數組。
確切地說,如何計算該值是需要在回調中指定的。來看囈使用reduce
的一個簡單的例子:對一組數字求和:
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10].reduce((sum, number) => {
return sum + number;
}, 0) // -> 55
註意這裡的回調接受兩個參數:sum
和number
。第一個參數總是前一個迭代返回的結果,第二個參數在遍歷中的當前數組元素。
這裡,當咱們對數組進行迭代時,sum
包含到迴圈當前索引的所有數字的和因為每次迭代咱們都將數組的當前值添加到sum
中。
實現
function reduce(array, callback, initValue) {
const { length } = array;
let acc = initValue;
let startAtIndex = 0;
if (initValue === undefined) {
acc = array[0];
startAtIndex = 0;
}
for (let index = startAtIndex; index < length; index += 1) {
const value = array[index];
acc = callback(acc, value, index, array)
}
return acc;
}
咱們創建了兩個變數acc
和startAtIndex
,並用它們的預設值初始化它們,分別是參數initValue
和0
。
然後,檢查initValue
是否是undefined
。如果是,則必須將數組的第一個值設置為初值,為了不重覆計算初始元素,將startAtIndex
設置為1
。
每次迭代,reduce
方法都將回調的結果保存在累加器(acc
)中,然後在下一個迭代中使用。對於第一次迭代,acc
被設置為initValue
或array[0]
。
測試
logOperation('reduce', [1, 2, 3, 4, 5], array => reduce(array, (sum, number) => sum + number, 0));
運行:
{ operation: 'reduce',
arrayBefore: [ 1, 2, 3, 4, 5 ],
arrayAfter: [ 1, 2, 3, 4, 5 ],
mutates: false,
result: 15
}
檢索類
有什麼操作比搜索特定值更常見?這裡有一些方法可以幫助我們。
.findIndex
findIndex
幫助咱們找到數組中給定值的索引。
[1, 2, 3, 4, 5, 6, 7].findIndex(value => value === 5); // 4
findIndex
方法對數組中的每個數組索引0..length-1
(包括)執行一次callback
函數,直到找到一個callback
函數返回真實值(強製為true
)的值。如果找到這樣的元素,findIndex
會立即返回該元素的索引。如果回調從不返回真值,或者數組的length
為0
,則findIndex
返回-1
。
實現
function findIndex(array, callback) {
const { length } = array;
for (let index = 0; index < length; index += 1) {
const value = array[index];
if (callback(value, index, array)) {
return index;
}
}
return -1;
}
測試
logOperation('findIndex', [1, 2, 3, 4, 5], array => findIndex(array, number => number === 3));
運行:
{
operation: 'findIndex',
arrayBefore: [ 1, 2, 3, 4, 5 ],
arrayAfter: [ 1, 2, 3, 4, 5 ],
mutates: false,
result: 2
}
.find
find
與findIndex
的唯一區別在於,它返回的是實際值,而不是索引。實際工作中,咱們可以重用已經實現的findIndex
。
[1, 2, 3, 4, 5, 6, 7].find(value => value === 5); // 5
實現
function find(array, callback) {
const index = findIndex(array, callback);
if (index === -1) {
return undefined;
}
return array[index];
}
測試
logOperation('find', [1, 2, 3, 4, 5], array => find(array, number => number === 3));
運行
{
operation: 'find',
arrayBefore: [ 1, 2, 3, 4, 5 ],
arrayAfter: [ 1, 2, 3, 4, 5 ],
mutates: false,
result: 3
}
.indexOf
indexOf
是獲取給定值索引的另一種方法。然而,這一次,咱們將實際值作為參數而不是函數傳遞。同樣,為了簡化實現,可以使用前面實現的findIndex
[3, 2, 3].indexOf(3); // -> 0
實現
function indexOf(array, searchedValue) {
return findIndex(array, value => value === searchedValue)
}
測試
logOperation('indexOf', [1, 2, 3, 4, 5], array => indexOf(array, 3));
執行結果
{
operation: 'indexOf',
arrayBefore: [ 1, 2, 3, 4, 5 ],
arrayAfter: [ 1, 2, 3, 4, 5 ],
mutates: false,
result: 2
}
.lastIndexOf
lastIndexOf的工作方式與indexOf
相同,lastIndexOf()
方法返回指定元素在數組中的最後一個的索引,如果不存在則返回 -1
。
[3, 2, 3].lastIndexOf(3); // -> 2
實現
function lastIndexOf(array, searchedValue) {
for (let index = array.length - 1; index > -1; index -= 1 ){
const value = array[index];
if (value === searchedValue) {
return index;
}
}
return -1;
}
代碼基本與findIndex
類似,但是沒有執行回調,而是比較value
和searchedValue
。如果比較結果為 true
,則返回索引,如果找不到值,返回-1
。
測試
logOperation('lastIndexOf', [1, 2, 3, 4, 5, 3], array => lastIndexOf(array, 3));
執行結果
{
operation: 'lastIndexOf',
arrayBefore: [ 1, 2, 3, 4, 5, 3 ],
arrayAfter: [ 1, 2, 3, 4, 5, 3 ],
mutates: false,
result: 5
}
.every
every()
方法測試一個數組內的所有元素是否都能通過某個指定函數的測試,它返回一個布爾值。
[1, 2, 3].every(value => Number.isInteger(value)); // -> true
咱們可以將every
方法看作一個等價於邏輯與的數組。
實現
function every(array, callback){
const { length } = array;
for (let index = 0; index < length; index += 1) {
const value = array[index];
if (!callback(value, index, array)) {
return false;
}
}
return true;
}
咱們為每個值執行回調。如果在任何時候返回false
,則退出迴圈,整個方法返回false
。如果迴圈終止而沒有進入到if
語句裡面(說明條件都成立),則方法返回true
。
測試
logOperation('every', [1, 2, 3, 4, 5], array => every(array, number => Number.isInteger(number)));
執行結果
{
operation: 'every',
arrayBefore: [ 1, 2, 3, 4, 5 ],
arrayAfter: [ 1, 2, 3, 4, 5 ],
mutates: false,
result: true
}
.some
some
方法與 every
剛好相反,即只要其中一個為true
就會返回true
。與every
方法類似,咱們可以將some
方法看作一個等價於邏輯或數組。
[1, 2, 3, 4, 5].some(number => number === 5); // -> true
實現
function some(array, callback) {
const { length } = array;
for (let index = 0; index < length; index += 1) {
const value = array[index];
if (callback(value, index, array)) {
return true;
}
}
return false;
}
咱們為每個值執行回調。如果在任何時候返回true
,則退出迴圈,整個方法返回true
。如果迴圈終止而沒有進入到if
語句裡面(說明條件都不成立),則方法返回false
。
測試
logOperation('some', [1, 2, 3, 4, 5], array => some(array, number => number === 5));
執行結果
{
operation: 'some',
arrayBefore: [ 1, 2, 3, 4, 5 ],
arrayAfter: [ 1, 2, 3, 4, 5 ],
mutates: false,
result: true
}
.includes
includes
方法的工作方式類似於 some
方法,但是includes
不用回調,而是提供一個參數值來比較元素。
[1, 2, 3].includes(3); // -> true
實現
function includes(array, searchedValue){
return some(array, value => value === searchedValue)
}
測試
logOperation('includes', [1, 2, 3, 4, 5], array => includes(array, 5));
執行結果
{
operation: 'includes',
arrayBefore: [ 1, 2, 3, 4, 5 ],
arrayAfter: [ 1, 2, 3, 4, 5 ],
mutates: false,
result: true
}
拼接、附加和反轉數組
.concat
concat()
方法用於合併兩個或多個數組,此方法不會更改現有數組,而是返回一個新數組。
[1, 2, 3].concat([4, 5], 6, [7, 8]) // -> [1, 2, 3, 4, 5, 6, 7, 8]
實現
function concat(array, ...values) {
const result = [...array];
const { length } = values;
for (let index = 0; index < length; index += 1) {
const value = values[index];
if (Array.isArray(value)) {
push(result, ...value);
} else {
push(result, value);
}
}
return result;
}
concat
將數組作為第一個參數,並將未指定個數的值作為第二個參數,這些值可以是數組,也可以是其他類型的值。
首先,通過複製傳入的數組創建 result
數組。然後,遍歷 values
,檢查該值是否是數組。如果是,則使用push
函數將其值附加到結果數組中。
push(result, value)
只會向數組追加為一個元素。相反,通過使用展開操作符push(result,…value)
將數組的所有值附加到result
數組中。在某種程度上,咱們把數組扁平了一層。
測試
logOperation('concat', [1, 2, 3, 4, 5], array => concat(array, 1, 2, [3, 4]));
執行結果
{
operation: 'concat',
arrayBefore: [ 1, 2, 3, 4, 5 ],
arrayAfter: [ 1, 2, 3, 4, 5 ],
mutates: false,
result: [ 1, 2, 3, 4, 5, 1, 2, 3, 4 ]
}
.join
join()
方法用於把數組中的所有元素放入一個字元串,元素是通過指定的分隔符進行分隔的。
['Brian', 'Matt', 'Kate'].join(', ') // -> Brian, Matt, Kate
實現
function join(array, joinWith) {
return reduce(
array,
(result, current, index) => {
if (index === 0) {
return current;
}
return `${result}${joinWith}${current}`;
},
''
)
}
reduce
的回調是神奇之處:reduce
遍歷所提供的數組並將結果字元串拼接在一起,在數組的值之間放置所需的分隔符(作為joinWith
傳遞)。
array[0]
值需要一些特殊的處理,因為此時result
是一個空字元串,而且咱們也不希望分隔符(joinWith
)位於第一個元素前面。
測試
logOperation('join', [1, 2, 3, 4, 5], array => join(array, ', '));
執行結果
{
operation: 'join',
arrayBefore: [ 1, 2, 3, 4, 5 ],
arrayAfter: [ 1, 2, 3, 4, 5 ],
mutates: false,
result: '1, 2, 3, 4, 5'
}
.reverse
reverse()
方法將數組中元素的位置顛倒,並返回該數組,該方法會改變原數組。
實現
function reverse(array) {
const result = []
const lastIndex = array.length - 1;
for (let index = lastIndex; index > -1; index -= 1) {
const value = array[index];
result[lastIndex - index ] = value
}
return result;
}
其思路很簡單:首先,定義一個空數組,並將數組的最後一個索引保存為變數(lastIndex)
。接著反過來遍曆數組,將每個值保存在結果result
中的(lastIndex - index)
位置,然後返回result
數組。
測試
logOperation('reverse', [1, 2, 3, 4, 5], array => reverse(array));
執行結果
{
operation: 'reverse',
arrayBefore: [ 1, 2, 3, 4, 5 ],
arrayAfter: [ 1, 2, 3, 4, 5 ],
mutates: false,
result: [ 5, 4, 3, 2, 1 ]
}
添加、刪除和追加值
.shift
shift()
方法從數組中刪除第一個元素,並返回該元素的值,此方法更改數組的長度。
[1, 2, 3].shift(); // -> 1
實現
function shift(array) {
const { length } = array;
const firstValue = array[0];
for (let index = 1; index > length; index += 1) {
const value = array[index];
array[index - 1] = value;
}
array.length = length - 1;
return firstValue;
}
首先保存數組的原始長度及其初始值,然後遍曆數組並將每個值向下移動一個索引。完成遍歷後,更新數組的長度並返回初始值。
測試
logOperation('shift', [1, 2, 3, 4, 5], array => shift(array));
執行結果
{
operation: 'shift',
arrayBefore: [ 1, 2, 3, 4, 5 ],
arrayAfter: [ 2, 3, 4, 5 ],
mutates: true,
result: 1
}
.unshift
unshift()
方法將一個或多個元素添加到數組的開頭,並返回該數組的新長度(該方法修改原有數組)。
[2, 3, 4].unshift(1); // -> [1, 2, 3, 4]
實現
function unshift(array, ...values) {
const mergedArrays = concat(values, ...array);
const { length: mergedArraysLength } = mergedArrays;
for (let index = 0; index < mergedArraysLength; index += 1) {
const value = mergedArrays[index];
array[index] = value;
}
return array.length;
}
首先將需要加入數組值(作為參數傳遞的單個值)和數組拼接起來。這裡需要註意的是,values
放在第一位的,也就是放置在原始數組的前面。
然後保存這個新數組的長度並遍歷它,將它的值保存在原始數組中,並覆蓋開始時的值。
測試
logOperation('unshift', [1, 2, 3, 4, 5], array => unshift(array, 0));
執行結果
{
operation: 'unshift',
arrayBefore: [ 1, 2, 3, 4, 5 ],
arrayAfter: [ 0, 1, 2, 3, 4, 5 ],
mutates: true,
result: 6
}
.slice
slice()
方法返回一個新的數組對象,這一對象是一個由 begin
和 end
決定的原數組的淺拷貝(包括 begin
,不包括end
)原始數組不會被改變。
slice
會提取原數組中索引從 begin
到 end
的所有元素(包含 begin
,但不包含 end
)。
[1, 2, 3, 4, 5, 6, 7].slice(3, 6); // -> [4, 5, 6]
實現 (簡單實現)
function slice(array, startIndex = 0, endIndex = array.length) {
const result = [];
for (let index = startIndex; index < endIndex; index += 1) {
const value = array[index];
if (index < array.length) {
push(result, value);
}
}
return result;
}
咱們遍曆數組從startIndex
到endIndex
,並將每個值放入result
。這裡使用了這裡的預設參數,這樣當沒有傳遞參數時,slice
方法只創建數組的副本。
註意:if
語句確保只在原始數組中存在給定索引下的值時才加入 result
中。
測試
logOperation('slice', [1, 2, 3, 4, 5], array => slice(array, 1, 3));
執行結果
{
operation: 'slice',
arrayBefore: [ 1, 2, 3, 4, 5 ],
arrayAfter: [ 1, 2, 3, 4, 5 ],
mutates: false,
result: [ 2, 3 ]
}
.splice
splice()
方法通過刪除或替換現有元素或者原地添加新的元素來修改數組,並以數組形式返回被修改的內容。此方法會改變原數組。
首先,指定起始索引,然後指定要刪除多少個值,其餘的參數是要插入的值。
const arr = [1, 2, 3, 4, 5];
// 從位置0開始,刪除2個元素後插入 3, 4, 5
arr.splice(0, 2, 3, 4, 5);
arr // -> [3, 4, 5, 3, 4, 5]
實現
function splice( array, insertAtIndex, removeNumberOfElements, ...values) {
const firstPart = slice(array, 0, insertAtIndex);
const secondPart = slice(array, insertAtIndex + removeNumberOfElements);
const removedElements = slice(
array,
insertAtIndex,
insertAtIndex + removeNumberOfElements
);
const joinedParts = firstPart.concat(values, secondPart);
const { length: joinedPartsLength } = joinedParts;
for (let index = 0; index < joinedPartsLength; index += 1) {
array[index] = joinedParts[index];
}
array.length = joinedPartsLength;
return removedElements;
}
其思路是在insertAtIndex
和insertAtIndex + removeNumberOfElements
上進行兩次切割。這樣,將原始數組切成三段。第一部分(firstPart
)和第三部分(secondPart
)加個插入的元素組成為最後數組的內容。
測試
logOperation('splice', [1, 2, 3, 4, 5], array => splice(array, 1, 3));
執行結果
{
operation: 'splice',
arrayBefore: [ 1, 2, 3, 4, 5 ],
arrayAfter: [ 1, 5 ],
mutates: true,
result: [ 2, 3, 4 ]
}
.pop
pop()
方法從數組中刪除最後一個元素,並返回該元素的值。此方法更改數組的長度。
實現
function pop(array) {
const value = array[array.length - 1];
array.length = array.length - 1;
return value;
}
首先,將數組的最後一個值保存在一個變數中。然後只需將數組的長度減少1
,從而刪除最後一個值。
測試
logOperation('pop', [1, 2, 3, 4, 5], array => pop(array));
執行結果
{
operation: 'pop',
arrayBefore: [ 1, 2, 3, 4, 5 ],
arrayAfter: [ 1, 2, 3, 4 ],
mutates: true,
result: 5
}
.push
push()
方法將一個或多個元素添加到數組的末尾,並返回該數組的新長度。
[1, 2, 3, 4].push(5); // -> [1, 2, 3, 4, 5]
實現
function push(array, ...values) {
const { length: arrayLength } = array;
const { length: valuesLength } = values;
for (let index = 0; index < valuesLength; index += 1) {
array[arrayLength + index] = values[index];
}
return array.length;
}
首先,我們保存原始數組的長度,以及在它們各自的變數中要添加的值。然後,遍歷提供的值並將它們添加到原始數組中。
測試
logOperation('push', [1, 2, 3, 4, 5], array => push(array, 6, 7));
執行結果
{
operation: 'push',
arrayBefore: [ 1, 2, 3, 4, 5 ],
arrayAfter: [
1, 2, 3, 4,5, 6, 7
],
mutates: true,
result: 7
}
.fill
當咱們想用一個占位符值填充一個空數組時,可以使用fill
方法。如果想創建一個指定數量的null
元素數組,可以這樣做:
[...Array(5)].fill(null) // -> [null, null, null, null, null]
實現
function fill(array, value, startIndex = 0, endIndex = array.length) {
for (let index = startIndex; index < endIndex; index += 1) {
array[index] = value;
}
return array;
}
fill
方法真正做的是替換指定索引範圍內的數組的值。如果沒有提供範圍,該方法將替換所有數組的值。
測試
logOperation("fill", [...new Array(5)], array => fill(array, 0));
執行結果
{
operation: 'fill',
arrayBefore: [ undefined, undefined, undefined, undefined, undefined ],
arrayAfter: [ 0, 0, 0, 0, 0 ],
mutates: true,
result: [ 0, 0, 0, 0, 0 ]
}
扁平類
有時咱們的數組會變嵌套兩到三層,咱們想要將它們扁,也就是減少嵌套的程度。例如,想將所有值都放到頂層。為咱們提供幫助有兩個新特性:flat
和flatMap
方法。
.flat
flat
方法通過可指定深度值來減少嵌套的深度。
[1, 2, 3, [4, 5, [6, 7, [8]]]].flat(1); // -> [1, 2, 3, 4, 5, [6, 7, [8]]]
因為展開的深度值是1
,所以只有第一級數組是被扁平,其餘的保持不變。
[1, 2, 3, [4, 5]].flat(1) // -> [1, 2, 3, 4, 5]
實現
function flat(array, depth = 0) {
if (depth < 1 || !Array.isArray(array)) {
return array;
}
return reduce(
array,
(result, current) => {
return concat(result, flat(current, depth - 1));
},
[],
);
}
首先,我們檢查depth
參數是否小於1
。如果是,那就意味著沒有什麼要扁平的,咱們應該簡單地返回數組。
其次,咱們檢查數組參數是否屬於數組類型,因為如果它不是,那麼扁化就沒有意義了,所以只返回這個參數。
咱們們使用了之前實現的reduce
函數。從一個空數組開始,然後取數組的每個值並將其扁平。
註意,我們調用帶有(depth - 1)
的flat
函數。每次調用時,都遞減depth
參數,以免造成無限迴圈。扁平化完成後,將返回值來回加到result
數組中。
測試
logOperation('flat', [1, 2, 3, [4, 5, [6]]], array => flat(array, 2));
執行結果
{
operation: 'flat',
arrayBefore: [ 1, 2, 3, [ 4, 5, [Array] ] ],
arrayAfter: [ 1, 2, 3, [ 4, 5, [Array] ] ],
mutates: false,
result: [ 1, 2, 3, 4, 5, 6 ]
}
.flatMap
flatMap()
方法首先使用映射函數映射每個元素,然後將結果壓縮成一個新數組。它與 map 和 深度值1的 flat 幾乎相同,但 flatMap 通常在合併成一種方法的效率稍微高一些。
在上面的map
方法中,對於每個值,只返回一個值。這樣,一個包含三個元素的數組在映射之後仍然有三個元素。使用flatMap
,在提供的回調函數中,可以返回一個數組,這個數組稍後將被扁平。
[1, 2, 3].flatMap(value => [value, value, value]); // [1, 1, 1, 2, 2, 2, 3, 3, 3]
每個返回的數組都是扁平的,我們得到的不是一個嵌套了三個數組的數組,而是一個包含9個元素的數組。
實現
function flatMap(array, callback) {
return flat(map(array, callback), 1);
}
首先使用map
,然後將數組的結果數組扁平化一層。
測試
logOperation('flatMap', [1, 2, 3], array => flatMap(array, number => [number, number]));
執行結果
{
operation: 'flatMap',
arrayBefore: [ 1, 2, 3 ],
arrayAfter: [ 1, 2, 3 ],
mutates: false,
result: [ 1, 1, 2, 2, 3, 3 ]
}
generator 類
最後三種方法的特殊之處在於它們返回生成器的方式。如果你不熟悉生成器,請跳過它們,因為你可能不會很快使用它們。
.values
values
方法返回一個生成器,該生成器生成數組的值。
const valuesGenerator = values([1, 2, 3, 4, 5]);
valuesGenerator.next(); // { value: 1, done: false }
實現
function values(array) {
const { length } = array;
function* createGenerator() {
for (let index = 0; index < length; index += 1) {
const value = array[index];
yield value;
}
}
return createGenerator();
}
首先,咱們定義createGenerator
函數。在其中,咱們遍曆數組並生成每個值。
.keys
keys
方法返回一個生成器,該生成器生成數組的索引。
const keysGenerator = keys([1, 2, 3, 4, 5]);
keysGenerator.next(); // { value: 0, done: false }
實現
function keys(array) {
function* createGenerator() {
const { length } = array;
for (let index = 0; index < length; index += 1) {
yield index;
}
}
return createGenerator();
}
實現完全相同,但這一次,生成的是索引,而不是值。
.entries
entry
方法返回生成鍵值對的生成器。
const entriesGenerator = entries([1, 2, 3, 4, 5]);
entriesGenerator.next(); // { value: [0, 1], done: false }
實現
function entries(array) {
const { length } = array;
function* createGenerator() {
for (let index = 0; index < length; index += 1) {
const value = array[index];
yield [index, value];
}
}
return createGenerator();
}
同樣的實現,但現在咱們將索引和值結合起來,併在數組中生成它們。
總結
高效使用數組的方法是成為一名優秀開發人員的基礎。瞭解他們內部工作的複雜性是我所知道的最好的方法。
代碼部署後可能存在的BUG沒法實時知道,事後為瞭解決這些BUG,花了大量的時間進行log 調試,這邊順便給大家推薦一個好用的BUG監控工具 [Fundebug][https://www.fundebug.com/?utm_source=xiaozhi]。
原文:https://dev.to/bnevilleoneill/understand-array-methods-by-implementing-them-all-of-them-iha
關於Fundebug
Fundebug專註於JavaScript、微信小程式、微信小游戲、支付寶小程式、React Native、Node.js和Java線上應用實時BUG監控。 自從2016年雙十一正式上線,Fundebug累計處理了20億+錯誤事件,付費客戶有陽光保險、核桃編程、荔枝FM、掌門1對1、微脈、青團社等眾多品牌企業。歡迎大家免費試用!