經過實現25個數組方法來理解及高效使用數組方法(長文,建議收藏)

做者:Maciej Cieslar
譯者:前端小智
來源: dev

阿里雲最近在作活動,低至2折,有興趣能夠看看:
https://promotion.aliyun.com/...

爲了保證的可讀性,本文采用意譯而非直譯。html

要在給定數組上使用方法,只須要經過[].方法名便可,這些方法都定義在 Array.prototype 對象上。在這裏,我們先不使用這些相,反,我們將從簡單的方法開始定義本身的版本,並在這些版本的基礎上進行構建。前端

沒有比把東西拆開再從新組裝起來更好的學習方法了。注意,當我們的實現本身的方法時,不要覆蓋現有的方法,由於有的庫須要它們,而且這樣也方便比較我們本身的方法與原始方法的差別。git

因此不要這樣命名我們自定義的方法:github

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 方法對數組的每一個元素執行一次提供的函數,並且不會改變原數組。this

[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;
}

獲取每一個值並檢查所提供的回調函數是否返回truefalse,而後將該值添加到新建立的數組中,或者適當地丟棄它。

注意,這裏對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 ] 
}

.reduce

reduce() 方法接收一個函數做爲累加器,數組中的每一個值(從左到右)開始縮減,最終計算爲一個值reduce() 方法接受四個參數:初始值(或者上一次回調函數的返回值),當前元素值,當前索引,調用 reduce() 的數組

確切地說,如何計算該值是須要在回調中指定的。來看囈使用reduce的一個簡單的例子:對一組數字求和:

[1, 2, 3, 4, 5, 6, 7, 8, 9, 10].reduce((sum, number) => {
   return sum + number;
 }, 0) // -> 55

注意這裏的回調接受兩個參數:sumnumber。第一個參數老是前一個迭代返回的結果,第二個參數在遍歷中的當前數組元素。

這裏,當我們對數組進行迭代時,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;
}

我們建立了兩個變量accstartAtIndex,並用它們的默認值初始化它們,分別是參數initValue0

而後,檢查initValue是不是undefined。若是是,則必須將數組的第一個值設置爲初值,爲了避免重複計算初始元素,將startAtIndex設置爲1

每次迭代,reduce方法都將回調的結果保存在累加器(acc)中,而後在下一個迭代中使用。對於第一次迭代,acc被設置爲initValuearray[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會當即返回該元素的索引。若是回調從不返回真值,或者數組的length0,則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

findfindIndex的惟一區別在於,它返回的是實際值,而不是索引。實際工做中,我們能夠重用已經實現的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相似,可是沒有執行回調,而是比較valuesearchedValue。若是比較結果爲 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()

方法返回一個新的數組對象,這一對象是一個由 beginend 決定的原數組的淺拷貝(包括 begin,不包括end)原始數組不會被改變。

slice 會提取原數組中索引從 beginend 的全部元素(包含 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;
}

我們遍歷數組從startIndexendIndex,並將每一個值放入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;
}

其思路是在insertAtIndexinsertAtIndex + 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 ]
}

扁平類

有時我們的數組會變嵌套兩到三層,我們想要將它們扁,也就是減小嵌套的程度。例如,想將全部值都放到頂層。爲我們提供幫助有兩個新特性:flatflatMap 方法。

.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://dev.to/bnevilleoneill...

交流(歡迎加入羣,羣工做日都會發紅包,互動討論技術)

阿里雲最近在作活動,低至2折,有興趣能夠看看:https://promotion.aliyun.com/...

乾貨系列文章彙總以下,以爲不錯點個Star,歡迎 加羣 互相學習。

https://github.com/qq44924588...

我是小智,公衆號「大遷世界」做者,對前端技術保持學習愛好者。我會常常分享本身所學所看的乾貨,在進階的路上,共勉!

關注公衆號,後臺回覆福利,便可看到福利,你懂的。

clipboard.png

每次整理文章,通常都到2點才睡覺,一週4次左右,挺苦的,還望支持,給點鼓勵

相關文章
相關標籤/搜索