摘要: 通過代碼掌握數(shù)組方法。
Fundebug經(jīng)授權(quán)轉(zhuǎn)載,版權(quán)歸原作者所有。
要在給定數(shù)組上使用方法,只需要通過[].方法名
即可丸升,這些方法都定義在 Array.prototype
對象上。在這里宦焦,咱們先不使用這些相发钝,反顿涣,咱們將從簡單的方法開始定義自己的版本,并在這些版本的基礎(chǔ)上進(jìn)行構(gòu)建酝豪。
沒有比把東西拆開再重新組裝起來更好的學(xué)習(xí)方法了涛碑。注意,當(dāng)咱們的實(shí)現(xiàn)自己的方法時孵淘,不要覆蓋現(xiàn)有的方法蒲障,因?yàn)橛械膸煨枰鼈儯⑶疫@樣也方便比較咱們自己的方法與原始方法的差異瘫证。
所以不要這樣命名咱們自定義的方法:
Array.prototype.map = function map() {
// implementation
};
最好這樣命名:
function map(array) {
// implementation
}
咱們也可以通過使用class
關(guān)鍵字并擴(kuò)展Array
構(gòu)造函數(shù)來實(shí)現(xiàn)咱們的方法揉阎,如下所示:
class OwnArray extends Array {
public constructor(...args) {
super(...args);
}
public map() {
// implementation
return this;
}
}
唯一的區(qū)別是,我們不使用數(shù)組參數(shù)背捌,而是使用this
關(guān)鍵字毙籽。
但是,我覺得 class 方式帶來不必要的混亂毡庆,所以咱們采用第一種方法坑赡。
有了這個,咱們先從實(shí)現(xiàn)最簡單的方法 forEach
開始么抗!
集合類
.forEach
Array.prototype.forEach
方法對數(shù)組的每個元素執(zhí)行一次提供的函數(shù)毅否,而且不會改變原數(shù)組。
[1, 2, 3, 4, 5].forEach(value => console.log(value));
實(shí)現(xiàn)
function forEach(array, callback) {
const { length } = array;
for (let index = 0; index < length; index += 1) {
const value = array[index];
callback(value, index, array)
}
}
咱們遍歷數(shù)組并為每個元素執(zhí)行回調(diào)蝇刀。這里需要注意的一點(diǎn)是螟加,該方法沒有返回什么,所以默認(rèn)返回undefined
吞琐。
方法漣
使用數(shù)組方法的好處是可以將操作鏈接在一起捆探。考慮以下代碼:
function getTodosWithCategory(todos, category) {
return todos
.filter(todo => todo.category === category)
.map(todo => normalizeTodo(todo));
}
這種方式站粟,咱們就不必將map
的執(zhí)行結(jié)果保存到變量中徐许,代碼會更簡潔。
不幸的是卒蘸,forEach
沒有返回原數(shù)組,這意味著咱們不能做下面的事情
// 無法工作
function getTodosWithCategory(todos, category) {
return todos
.filter(todo => todo.category === category)
.forEach((value) => console.log(value))
.map(todo => normalizeTodo(todo));
}
幫助函數(shù) (打印信息)
接著實(shí)現(xiàn)一個簡單的函數(shù)翻默,它能更好地解釋每個方法的功能:接受什么作為輸入缸沃,返回什么,以及它是否對數(shù)組進(jìn)行了修改修械。
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 方法用來判斷是否更改了原數(shù)組趾牧,如果有修改剛返回 true
,否則返回 false
肯污。當(dāng)然大伙有好的想法可以在評論提出呦翘单。
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
來測試咱們前面自己實(shí)現(xiàn)的 forEach
方法吨枉。
logOperation('forEach', [1, 2, 3, 4, 5], array => forEach(array, value => console.log(value)));
打印結(jié)果:
{
operation: 'forEach',
arrayBefore: [ 1, 2, 3, 4, 5 ],
arrayAfter: [ 1, 2, 3, 4, 5 ],
mutates: false,
result: undefined
}
.map
map
方法會給原數(shù)組中的每個元素都按順序調(diào)用一次 callback
函數(shù)。callback
每次執(zhí)行后的返回值(包括 undefined
)組合起來形成一個新數(shù)組哄芜。
實(shí)現(xiàn)
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;
}
提供給方法的回調(diào)函數(shù)接受舊值作為參數(shù)貌亭,并返回一個新值,然后將其保存在新數(shù)組中的相同索引下认臊,這里用變量 result
表示圃庭。
這里需要注意的是,咱們返回了一個新的數(shù)組失晴,不修改舊的剧腻。
測試
logOperation('map', [1, 2, 3, 4, 5], array => map(array, value => value + 5));
打印結(jié)果:
{
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
過濾回調(diào)返回為false
的值,每個值都保存在一個新的數(shù)組中涂屁,然后返回书在。
[1, 2, 3, 4, 5].filter(number => number >= 3);
// -> [3, 4, 5]
實(shí)現(xiàn)
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;
}
獲取每個值并檢查所提供的回調(diào)函數(shù)是否返回true
或false
,然后將該值添加到新創(chuàng)建的數(shù)組中拆又,或者適當(dāng)?shù)貋G棄它儒旬。
注意,這里對result
數(shù)組使用push
方法遏乔,而不是將值保存在傳入數(shù)組中放置的相同索引中义矛。這樣,result
就不會因?yàn)閬G棄的值而有空槽盟萨。
測試
logOperation('filter', [1, 2, 3, 4, 5], array => filter(array, value => value >= 2));
運(yùn)行:
{
operation: 'filter',
arrayBefore: [ 1, 2, 3, 4, 5 ],
arrayAfter: [ 1, 2, 3, 4, 5 ],
mutates: false,
result: [ 2, 3, 4, 5 ]
}
代碼部署后可能存在的BUG沒法實(shí)時知道凉翻,事后為了解決這些BUG,花了大量的時間進(jìn)行l(wèi)og 調(diào)試捻激,這邊順便給大家推薦一個好用的BUG監(jiān)控工具 [Fundebug][https://www.fundebug.com/?utm_source=xiaozhi]制轰。
.reduce
reduce()
方法接收一個函數(shù)作為累加器,數(shù)組中的每個值(從左到右)開始縮減胞谭,最終計算為一個值垃杖。reduce()
方法接受四個參數(shù):初始值(或者上一次回調(diào)函數(shù)的返回值),當(dāng)前元素值丈屹,當(dāng)前索引调俘,調(diào)用 reduce() 的數(shù)組。
確切地說旺垒,如何計算該值是需要在回調(diào)中指定的彩库。來看囈使用reduce
的一個簡單的例子:對一組數(shù)字求和:
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10].reduce((sum, number) => {
return sum + number;
}, 0) // -> 55
注意這里的回調(diào)接受兩個參數(shù):sum
和number
。第一個參數(shù)總是前一個迭代返回的結(jié)果先蒋,第二個參數(shù)在遍歷中的當(dāng)前數(shù)組元素骇钦。
這里,當(dāng)咱們對數(shù)組進(jìn)行迭代時竞漾,sum
包含到循環(huán)當(dāng)前索引的所有數(shù)字的和因?yàn)槊看蔚蹅兌紝?shù)組的當(dāng)前值添加到sum
中眯搭。
實(shí)現(xiàn)
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;
}
咱們創(chuàng)建了兩個變量acc
和startAtIndex
窥翩,并用它們的默認(rèn)值初始化它們,分別是參數(shù)initValue
和0
鳞仙。
然后寇蚊,檢查initValue
是否是undefined
。如果是繁扎,則必須將數(shù)組的第一個值設(shè)置為初值幔荒,為了不重復(fù)計算初始元素,將startAtIndex
設(shè)置為1
梳玫。
每次迭代爹梁,reduce
方法都將回調(diào)的結(jié)果保存在累加器(acc
)中,然后在下一個迭代中使用提澎。對于第一次迭代姚垃,acc
被設(shè)置為initValue
或array[0]
。
測試
logOperation('reduce', [1, 2, 3, 4, 5], array => reduce(array, (sum, number) => sum + number, 0));
運(yùn)行:
{ operation: 'reduce',
arrayBefore: [ 1, 2, 3, 4, 5 ],
arrayAfter: [ 1, 2, 3, 4, 5 ],
mutates: false,
result: 15
}
檢索類
有什么操作比搜索特定值更常見?這里有一些方法可以幫助我們盼忌。
.findIndex
findIndex
幫助咱們找到數(shù)組中給定值的索引积糯。
[1, 2, 3, 4, 5, 6, 7].findIndex(value => value === 5); // 4
findIndex
方法對數(shù)組中的每個數(shù)組索引0..length-1
(包括)執(zhí)行一次callback
函數(shù),直到找到一個callback
函數(shù)返回真實(shí)值(強(qiáng)制為true
)的值谦纱。如果找到這樣的元素看成,findIndex
會立即返回該元素的索引。如果回調(diào)從不返回真值跨嘉,或者數(shù)組的length
為0
川慌,則findIndex
返回-1
。
實(shí)現(xiàn)
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));
運(yùn)行:
{
operation: 'findIndex',
arrayBefore: [ 1, 2, 3, 4, 5 ],
arrayAfter: [ 1, 2, 3, 4, 5 ],
mutates: false,
result: 2
}
.find
find
與findIndex
的唯一區(qū)別在于祠乃,它返回的是實(shí)際值梦重,而不是索引。實(shí)際工作中亮瓷,咱們可以重用已經(jīng)實(shí)現(xiàn)的findIndex
琴拧。
[1, 2, 3, 4, 5, 6, 7].find(value => value === 5); // 5
實(shí)現(xiàn)
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));
運(yùn)行
{
operation: 'find',
arrayBefore: [ 1, 2, 3, 4, 5 ],
arrayAfter: [ 1, 2, 3, 4, 5 ],
mutates: false,
result: 3
}
.indexOf
indexOf
是獲取給定值索引的另一種方法。然而嘱支,這一次蚓胸,咱們將實(shí)際值作為參數(shù)而不是函數(shù)傳遞。同樣除师,為了簡化實(shí)現(xiàn)赢织,可以使用前面實(shí)現(xiàn)的findIndex
[3, 2, 3].indexOf(3); // -> 0
實(shí)現(xiàn)
function indexOf(array, searchedValue) {
return findIndex(array, value => value === searchedValue)
}
測試
logOperation('indexOf', [1, 2, 3, 4, 5], array => indexOf(array, 3));
執(zhí)行結(jié)果
{
operation: 'indexOf',
arrayBefore: [ 1, 2, 3, 4, 5 ],
arrayAfter: [ 1, 2, 3, 4, 5 ],
mutates: false,
result: 2
}
.lastIndexOf
lastIndexOf的工作方式與indexOf
相同,lastIndexOf()
方法返回指定元素在數(shù)組中的最后一個的索引馍盟,如果不存在則返回 -1
。
[3, 2, 3].lastIndexOf(3); // -> 2
實(shí)現(xiàn)
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
類似茧吊,但是沒有執(zhí)行回調(diào)贞岭,而是比較value
和searchedValue
八毯。如果比較結(jié)果為 true
,則返回索引,如果找不到值瞄桨,返回-1
话速。
測試
logOperation('lastIndexOf', [1, 2, 3, 4, 5, 3], array => lastIndexOf(array, 3));
執(zhí)行結(jié)果
{
operation: 'lastIndexOf',
arrayBefore: [ 1, 2, 3, 4, 5, 3 ],
arrayAfter: [ 1, 2, 3, 4, 5, 3 ],
mutates: false,
result: 5
}
.every
every()
方法測試一個數(shù)組內(nèi)的所有元素是否都能通過某個指定函數(shù)的測試,它返回一個布爾值芯侥。
[1, 2, 3].every(value => Number.isInteger(value)); // -> true
咱們可以將every
方法看作一個等價于邏輯與的數(shù)組泊交。
實(shí)現(xiàn)
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;
}
咱們?yōu)槊總€值執(zhí)行回調(diào)。如果在任何時候返回false
柱查,則退出循環(huán)廓俭,整個方法返回false
。如果循環(huán)終止而沒有進(jìn)入到if
語句里面(說明條件都成立)唉工,則方法返回true
研乒。
測試
logOperation('every', [1, 2, 3, 4, 5], array => every(array, number => Number.isInteger(number)));
執(zhí)行結(jié)果
{
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
方法看作一個等價于邏輯或數(shù)組。
[1, 2, 3, 4, 5].some(number => number === 5); // -> true
實(shí)現(xiàn)
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;
}
咱們?yōu)槊總€值執(zhí)行回調(diào)谣膳。如果在任何時候返回true
竿报,則退出循環(huán),整個方法返回true
继谚。如果循環(huán)終止而沒有進(jìn)入到if
語句里面(說明條件都不成立)烈菌,則方法返回false
。
測試
logOperation('some', [1, 2, 3, 4, 5], array => some(array, number => number === 5));
執(zhí)行結(jié)果
{
operation: 'some',
arrayBefore: [ 1, 2, 3, 4, 5 ],
arrayAfter: [ 1, 2, 3, 4, 5 ],
mutates: false,
result: true
}
.includes
includes
方法的工作方式類似于 some
方法犬庇,但是includes
不用回調(diào)僧界,而是提供一個參數(shù)值來比較元素。
[1, 2, 3].includes(3); // -> true
實(shí)現(xiàn)
function includes(array, searchedValue){
return some(array, value => value === searchedValue)
}
測試
logOperation('includes', [1, 2, 3, 4, 5], array => includes(array, 5));
執(zhí)行結(jié)果
{
operation: 'includes',
arrayBefore: [ 1, 2, 3, 4, 5 ],
arrayAfter: [ 1, 2, 3, 4, 5 ],
mutates: false,
result: true
}
拼接臭挽、附加和反轉(zhuǎn)數(shù)組
.concat
concat()
方法用于合并兩個或多個數(shù)組捂襟,此方法不會更改現(xiàn)有數(shù)組,而是返回一個新數(shù)組欢峰。
[1, 2, 3].concat([4, 5], 6, [7, 8]) // -> [1, 2, 3, 4, 5, 6, 7, 8]
實(shí)現(xiàn)
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
將數(shù)組作為第一個參數(shù)葬荷,并將未指定個數(shù)的值作為第二個參數(shù),這些值可以是數(shù)組纽帖,也可以是其他類型的值宠漩。
首先,通過復(fù)制傳入的數(shù)組創(chuàng)建 result
數(shù)組懊直。然后扒吁,遍歷 values
,檢查該值是否是數(shù)組室囊。如果是雕崩,則使用push
函數(shù)將其值附加到結(jié)果數(shù)組中魁索。
push(result, value)
只會向數(shù)組追加為一個元素。相反盼铁,通過使用展開操作符push(result粗蔚,…value)
將數(shù)組的所有值附加到result
數(shù)組中。在某種程度上饶火,咱們把數(shù)組扁平了一層鹏控。
測試
logOperation('concat', [1, 2, 3, 4, 5], array => concat(array, 1, 2, [3, 4]));
執(zhí)行結(jié)果
{
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()
方法用于把數(shù)組中的所有元素放入一個字符串,元素是通過指定的分隔符進(jìn)行分隔的肤寝。
['Brian', 'Matt', 'Kate'].join(', ') // -> Brian, Matt, Kate
實(shí)現(xiàn)
function join(array, joinWith) {
return reduce(
array,
(result, current, index) => {
if (index === 0) {
return current;
}
return `${result}${joinWith}${current}`;
},
''
)
}
reduce
的回調(diào)是神奇之處:reduce
遍歷所提供的數(shù)組并將結(jié)果字符串拼接在一起当辐,在數(shù)組的值之間放置所需的分隔符(作為joinWith
傳遞)。
array[0]
值需要一些特殊的處理醒陆,因?yàn)榇藭rresult
是一個空字符串瀑构,而且咱們也不希望分隔符(joinWith
)位于第一個元素前面。
測試
logOperation('join', [1, 2, 3, 4, 5], array => join(array, ', '));
執(zhí)行結(jié)果
{
operation: 'join',
arrayBefore: [ 1, 2, 3, 4, 5 ],
arrayAfter: [ 1, 2, 3, 4, 5 ],
mutates: false,
result: '1, 2, 3, 4, 5'
}
.reverse
reverse()
方法將數(shù)組中元素的位置顛倒刨摩,并返回該數(shù)組寺晌,該方法會改變原數(shù)組。
實(shí)現(xiàn)
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;
}
其思路很簡單:首先澡刹,定義一個空數(shù)組呻征,并將數(shù)組的最后一個索引保存為變量(lastIndex)
。接著反過來遍歷數(shù)組罢浇,將每個值保存在結(jié)果result
中的(lastIndex - index)
位置陆赋,然后返回result
數(shù)組。
測試
logOperation('reverse', [1, 2, 3, 4, 5], array => reverse(array));
執(zhí)行結(jié)果
{
operation: 'reverse',
arrayBefore: [ 1, 2, 3, 4, 5 ],
arrayAfter: [ 1, 2, 3, 4, 5 ],
mutates: false,
result: [ 5, 4, 3, 2, 1 ]
}
添加嚷闭、刪除和追加值
.shift
shift()
方法從數(shù)組中刪除第一個元素攒岛,并返回該元素的值,此方法更改數(shù)組的長度胞锰。
[1, 2, 3].shift(); // -> 1
實(shí)現(xiàn)
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;
}
首先保存數(shù)組的原始長度及其初始值灾锯,然后遍歷數(shù)組并將每個值向下移動一個索引。完成遍歷后嗅榕,更新數(shù)組的長度并返回初始值顺饮。
測試
logOperation('shift', [1, 2, 3, 4, 5], array => shift(array));
執(zhí)行結(jié)果
{
operation: 'shift',
arrayBefore: [ 1, 2, 3, 4, 5 ],
arrayAfter: [ 2, 3, 4, 5 ],
mutates: true,
result: 1
}
.unshift
unshift()
方法將一個或多個元素添加到數(shù)組的開頭,并返回該數(shù)組的新長度(該方法修改原有數(shù)組)凌那。
[2, 3, 4].unshift(1); // -> [1, 2, 3, 4]
實(shí)現(xiàn)
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;
}
首先將需要加入數(shù)組值(作為參數(shù)傳遞的單個值)和數(shù)組拼接起來兼雄。這里需要注意的是,values
放在第一位的帽蝶,也就是放置在原始數(shù)組的前面赦肋。
然后保存這個新數(shù)組的長度并遍歷它,將它的值保存在原始數(shù)組中,并覆蓋開始時的值金砍。
測試
logOperation('unshift', [1, 2, 3, 4, 5], array => unshift(array, 0));
執(zhí)行結(jié)果
{
operation: 'unshift',
arrayBefore: [ 1, 2, 3, 4, 5 ],
arrayAfter: [ 0, 1, 2, 3, 4, 5 ],
mutates: true,
result: 6
}
.slice
slice()
方法返回一個新的數(shù)組對象局蚀,這一對象是一個由 begin
和 end
決定的原數(shù)組的淺拷貝(包括 begin
,不包括end
)原始數(shù)組不會被改變恕稠。
slice
會提取原數(shù)組中索引從 begin
到 end
的所有元素(包含 begin
,但不包含 end
)扶欣。
[1, 2, 3, 4, 5, 6, 7].slice(3, 6); // -> [4, 5, 6]
實(shí)現(xiàn) (簡單實(shí)現(xiàn))
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;
}
咱們遍歷數(shù)組從startIndex
到endIndex
鹅巍,并將每個值放入result
。這里使用了這里的默認(rèn)參數(shù)料祠,這樣當(dāng)沒有傳遞參數(shù)時骆捧,slice
方法只創(chuàng)建數(shù)組的副本。
注意:if
語句確保只在原始數(shù)組中存在給定索引下的值時才加入 result
中髓绽。
測試
logOperation('slice', [1, 2, 3, 4, 5], array => slice(array, 1, 3));
執(zhí)行結(jié)果
{
operation: 'slice',
arrayBefore: [ 1, 2, 3, 4, 5 ],
arrayAfter: [ 1, 2, 3, 4, 5 ],
mutates: false,
result: [ 2, 3 ]
}
.splice
splice()
方法通過刪除或替換現(xiàn)有元素或者原地添加新的元素來修改數(shù)組,并以數(shù)組形式返回被修改的內(nèi)容敛苇。此方法會改變原數(shù)組。
首先顺呕,指定起始索引枫攀,然后指定要刪除多少個值,其余的參數(shù)是要插入的值株茶。
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]
實(shí)現(xiàn)
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
上進(jìn)行兩次切割。這樣启盛,將原始數(shù)組切成三段蹦掐。第一部分(firstPart
)和第三部分(secondPart
)加個插入的元素組成為最后數(shù)組的內(nèi)容。
測試
logOperation('splice', [1, 2, 3, 4, 5], array => splice(array, 1, 3));
執(zhí)行結(jié)果
{
operation: 'splice',
arrayBefore: [ 1, 2, 3, 4, 5 ],
arrayAfter: [ 1, 5 ],
mutates: true,
result: [ 2, 3, 4 ]
}
.pop
pop()
方法從數(shù)組中刪除最后一個元素僵闯,并返回該元素的值卧抗。此方法更改數(shù)組的長度。
實(shí)現(xiàn)
function pop(array) {
const value = array[array.length - 1];
array.length = array.length - 1;
return value;
}
首先鳖粟,將數(shù)組的最后一個值保存在一個變量中社裆。然后只需將數(shù)組的長度減少1
,從而刪除最后一個值牺弹。
測試
logOperation('pop', [1, 2, 3, 4, 5], array => pop(array));
執(zhí)行結(jié)果
{
operation: 'pop',
arrayBefore: [ 1, 2, 3, 4, 5 ],
arrayAfter: [ 1, 2, 3, 4 ],
mutates: true,
result: 5
}
.push
push()
方法將一個或多個元素添加到數(shù)組的末尾浦马,并返回該數(shù)組的新長度。
[1, 2, 3, 4].push(5); // -> [1, 2, 3, 4, 5]
實(shí)現(xiàn)
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;
}
首先张漂,我們保存原始數(shù)組的長度晶默,以及在它們各自的變量中要添加的值。然后航攒,遍歷提供的值并將它們添加到原始數(shù)組中磺陡。
測試
logOperation('push', [1, 2, 3, 4, 5], array => push(array, 6, 7));
執(zhí)行結(jié)果
{
operation: 'push',
arrayBefore: [ 1, 2, 3, 4, 5 ],
arrayAfter: [
1, 2, 3, 4,5, 6, 7
],
mutates: true,
result: 7
}
.fill
當(dāng)咱們想用一個占位符值填充一個空數(shù)組時,可以使用fill
方法。如果想創(chuàng)建一個指定數(shù)量的null
元素數(shù)組币他,可以這樣做:
[...Array(5)].fill(null) // -> [null, null, null, null, null]
實(shí)現(xiàn)
function fill(array, value, startIndex = 0, endIndex = array.length) {
for (let index = startIndex; index < endIndex; index += 1) {
array[index] = value;
}
return array;
}
fill
方法真正做的是替換指定索引范圍內(nèi)的數(shù)組的值坞靶。如果沒有提供范圍,該方法將替換所有數(shù)組的值蝴悉。
測試
logOperation("fill", [...new Array(5)], array => fill(array, 0));
執(zhí)行結(jié)果
{
operation: 'fill',
arrayBefore: [ undefined, undefined, undefined, undefined, undefined ],
arrayAfter: [ 0, 0, 0, 0, 0 ],
mutates: true,
result: [ 0, 0, 0, 0, 0 ]
}
扁平類
有時咱們的數(shù)組會變嵌套兩到三層彰阴,咱們想要將它們扁,也就是減少嵌套的程度拍冠。例如尿这,想將所有值都放到頂層。為咱們提供幫助有兩個新特性:flat
和flatMap
方法庆杜。
.flat
flat
方法通過可指定深度值來減少嵌套的深度射众。
[1, 2, 3, [4, 5, [6, 7, [8]]]].flat(1); // -> [1, 2, 3, 4, 5, [6, 7, [8]]]
因?yàn)檎归_的深度值是1
,所以只有第一級數(shù)組是被扁平晃财,其余的保持不變叨橱。
[1, 2, 3, [4, 5]].flat(1) // -> [1, 2, 3, 4, 5]
實(shí)現(xiàn)
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
參數(shù)是否小于1
断盛。如果是罗洗,那就意味著沒有什么要扁平的,咱們應(yīng)該簡單地返回數(shù)組郑临。
其次栖博,咱們檢查數(shù)組參數(shù)是否屬于數(shù)組類型,因?yàn)槿绻皇窍岫矗敲幢饣蜎]有意義了仇让,所以只返回這個參數(shù)。
咱們們使用了之前實(shí)現(xiàn)的reduce
函數(shù)躺翻。從一個空數(shù)組開始丧叽,然后取數(shù)組的每個值并將其扁平。
注意公你,我們調(diào)用帶有(depth - 1)
的flat
函數(shù)铐炫。每次調(diào)用時址芯,都遞減depth
參數(shù)肝断,以免造成無限循環(huán)鞠评。扁平化完成后,將返回值來回加到result
數(shù)組中剪芥。
測試
logOperation('flat', [1, 2, 3, [4, 5, [6]]], array => flat(array, 2));
執(zhí)行結(jié)果
{
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()
方法首先使用映射函數(shù)映射每個元素垄开,然后將結(jié)果壓縮成一個新數(shù)組。它與 map 和 深度值1的 flat 幾乎相同税肪,但 flatMap 通常在合并成一種方法的效率稍微高一些溉躲。
在上面的map
方法中榜田,對于每個值,只返回一個值锻梳。這樣箭券,一個包含三個元素的數(shù)組在映射之后仍然有三個元素。使用flatMap
疑枯,在提供的回調(diào)函數(shù)中辩块,可以返回一個數(shù)組,這個數(shù)組稍后將被扁平荆永。
[1, 2, 3].flatMap(value => [value, value, value]); // [1, 1, 1, 2, 2, 2, 3, 3, 3]
每個返回的數(shù)組都是扁平的庆捺,我們得到的不是一個嵌套了三個數(shù)組的數(shù)組,而是一個包含9個元素的數(shù)組屁魏。
實(shí)現(xiàn)
function flatMap(array, callback) {
return flat(map(array, callback), 1);
}
首先使用map
,然后將數(shù)組的結(jié)果數(shù)組扁平化一層捉腥。
測試
logOperation('flatMap', [1, 2, 3], array => flatMap(array, number => [number, number]));
執(zhí)行結(jié)果
{
operation: 'flatMap',
arrayBefore: [ 1, 2, 3 ],
arrayAfter: [ 1, 2, 3 ],
mutates: false,
result: [ 1, 1, 2, 2, 3, 3 ]
}
generator 類
最后三種方法的特殊之處在于它們返回生成器的方式氓拼。如果你不熟悉生成器,請?zhí)^它們抵碟,因?yàn)槟憧赡懿粫芸焓褂盟鼈儭?/p>
.values
values
方法返回一個生成器桃漾,該生成器生成數(shù)組的值。
const valuesGenerator = values([1, 2, 3, 4, 5]);
valuesGenerator.next(); // { value: 1, done: false }
實(shí)現(xiàn)
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
函數(shù)撬统。在其中,咱們遍歷數(shù)組并生成每個值敦迄。
.keys
keys
方法返回一個生成器恋追,該生成器生成數(shù)組的索引。
const keysGenerator = keys([1, 2, 3, 4, 5]);
keysGenerator.next(); // { value: 0, done: false }
實(shí)現(xiàn)
function keys(array) {
function* createGenerator() {
const { length } = array;
for (let index = 0; index < length; index += 1) {
yield index;
}
}
return createGenerator();
}
實(shí)現(xiàn)完全相同罚屋,但這一次苦囱,生成的是索引,而不是值脾猛。
.entries
entry
方法返回生成鍵值對的生成器撕彤。
const entriesGenerator = entries([1, 2, 3, 4, 5]);
entriesGenerator.next(); // { value: [0, 1], done: false }
實(shí)現(xiàn)
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();
}
同樣的實(shí)現(xiàn),但現(xiàn)在咱們將索引和值結(jié)合起來猛拴,并在數(shù)組中生成它們羹铅。
總結(jié)
高效使用數(shù)組的方法是成為一名優(yōu)秀開發(fā)人員的基礎(chǔ)。了解他們內(nèi)部工作的復(fù)雜性是我所知道的最好的方法愉昆。
代碼部署后可能存在的BUG沒法實(shí)時知道职员,事后為了解決這些BUG,花了大量的時間進(jìn)行l(wèi)og 調(diào)試撼唾,這邊順便給大家推薦一個好用的BUG監(jiān)控工具 [Fundebug][https://www.fundebug.com/?utm_source=xiaozhi]廉邑。
原文:https://dev.to/bnevilleoneill/understand-array-methods-by-implementing-them-all-of-them-iha
關(guān)于Fundebug
Fundebug專注于JavaScript哥蔚、微信小程序、微信小游戲蛛蒙、支付寶小程序糙箍、React Native、Node.js和Java線上應(yīng)用實(shí)時BUG監(jiān)控牵祟。 自從2016年雙十一正式上線深夯,F(xiàn)undebug累計處理了20億+錯誤事件,付費(fèi)客戶有陽光保險诺苹、核桃編程咕晋、荔枝FM、掌門1對1收奔、微脈掌呜、青團(tuán)社等眾多品牌企業(yè)。歡迎大家免費(fèi)試用坪哄!