這是JS 原生方法原理探究系列的第六篇文章,此次咱們來實現數組的 30 個 API。在開始以前,能夠先看一下本文的思惟導圖:數組
文章分爲四個部分,分別介紹在數組原生 API 中,會修改原數組的方法、不會修改原數組的方法、用於遍歷的方法以及靜態方法,共計 30 個。在講每一個方法的具體實現以前,會簡要介紹它們的用法(更詳細的查閱 MDN 便可),以後給出實現的思路和具體的代碼。代碼來源於本身思考以及對 polyfill 的參考,實測能夠經過大部分測試用例,但不排除有更好的思路以及值得優化的地方,若發現任何錯誤或者值得改進之處,歡迎評論區留言指正。瀏覽器
這是一些實現時須要注意的地方:函數
for…in
遍歷數組,它只會遍歷出那些非 empty 元素的索引(注意:for...of
能夠遍歷出 empty 元素,遍歷出的結果是 undefined
)下面正文開始。測試
pop 方法能夠彈出數組最後一個元素,並將其做爲返回值優化
const arr = [1,2,3] arr.pop() // 返回移除的元素 5,數組變成 [1,2,3,4]
Array.prototype.myPop = function(){ let arr = this let returnValue = arr[arr.length - 1] arr.length-- return returnValue }
push 方法能夠往數組末尾添加任意多個元素,並將數組長度做爲返回值:this
const arr = [1,2,3] arr.push(4,5) // 返回最終的數組長度 5,數組變成 [1,2,3,4,5]
Array.prototype.myPush = function(...args){ let arr = this for(let el of args){ arr[arr.length] = el } return arr.length }
shift 方法能夠從數組頭部彈出一個元素,並將其做爲返回值:編碼
const arr = [3,4,5] arr.shift() // 返回移除的元素 1,數組變成 [2,3,4,5]
Array.prototype.myShift = function(){ let arr = this let returnValue = arr[0] for(let i = 1;i < arr.length;i++){ arr[i-1] = arr[i] } arr.length-- return returnValue }
unshift 方法能夠往數組頭部添加任意多個元素,並將數組長度做爲返回值:spa
const arr = [3,4,5] arr.unshift(1,2) // 返回最終的數組長度 5,數組變成 [1,2,3,4,5]
Array.prototype.myUnshift = function(...args){ let arr = this if(args.length > 0){ let len1 = arr.length,len2 = args.length // k 表明數組最後一個元素的下標 let k = len1 + len2 - 1 for(let i = len1 - 1;i >= 0;i--){ arr[k--] = arr[i] } for(let i in args){ arr[i] = args[i] } } return arr.length }
reverse 將原數組進行反轉,最終返回原數組prototype
[1,2,3].reverse() // [3,2,1]
Array.prototype.myReverse = function(){ let arr = this let k = arr.length - 1 for(let i = 0;i < Math.floor(arr.length/2);i++){ let temp = arr[i] arr[i] = arr[k] arr[k--] = temp } return arr }
reverse 也算是一種排序方法,但顯然它不夠靈活,因而有了 sort
方法。code
undefined
時,默認的排序規則是:將每一個元素轉化爲字符串,再將它們按照 Unicode 編碼從小到大排序。其中,null
會轉化爲 "null"
,undefined
固定排在數組最後const arr = [1,2,5,10] // 沒有傳入函數,會對每一個元素調用 toString,比較字符的 unicode 編碼,所以 "10"<"2" arr.sort() // [1,10,2,5] // 傳入比較函數,從小到大排序 arr.sort((a,b) => a<b?-1:a>b?1:0) // [1,2,5,10] arr.sort((a,b) => a-b) // [1,2,5,10] // 傳入比較函數,從大到小排序 arr.sort((a,b) => a>b?-1:a<b?1:0) // [10,5,2,1]
Array.prototype.mySort = function(...args){ let arr = this // 判斷規則,判斷 x 是否應該放在 y 的前面 function shouldBefore(x,y){ // 若是沒傳參或者傳了 undefined if(args.length == 0 || args.length != 0 && typeof args[0] === 'undefined'){ return String(x) < String(y) } // 若是傳函數 else { let fn = args[0] return fn(x,y) < 0 ? true : false } } // 若是傳參可是沒傳函數或者 undefined if(typeof args[0] != 'function' && typeof args[0] != 'undefined'){ throw new TypeError("The argument msut be undefined or a function") } else { for(let i = 0;i < arr.length - 1;i++){ for(let j = 0;j < arr.length - 1 - i;j++){ if(shouldBefore(arr[j+1],arr[j])){ // 兩數交換 let temp = arr[j] arr[j] = arr[j+1] arr[j+1] = temp } } } } return arr }
splice 能夠作三種事:刪除元素、添加元素、替換元素。
start
、刪除的元素個數num
,以及添加的元素 item一、item二、...
start
能夠是正數或者負數。若是是正數且超過數組長度,則無視刪除操做,直接把須要添加的元素添加到數組末尾;若是是負數,且負數絕對值小於數組長度,則將負數與長度相加做爲 start
,不然將 0 做爲 start
num
能夠是正數或者負數。若是沒有傳 num
,或者 num
是正數且超過 start
日後的元素個數(包含 start
),則將 start
和它後面全部元素刪除;若是 num
是 0 或者負數,則不刪除任何元素const arr = [1,2,3,4,5] // 刪除:在索引1這裏操做,刪除2個元素 arr.splice(1,2) // 返回 [2,3],arr 變成 [1,4,5] // 添加:在索引1這裏操做,刪除0個元素,添加2個元素(注意是插入到索引1前面,不是後面) arr.splice(1,0,"a","b") // 返回 [],arr 變成 [1,"a","b",2,3,4,5] // 替換:刪除+添加就是替換 arr.splice(1,2,"a","b") // 返回 [1,"a","b",4,5]
Array.prototype.mySplice = function(...args){ let arr = this let len = arr.length let res = [] function computeStart(start){ return start >= 0 ? start : Math.abs(start) < len ? start + len : 0 } function computeDeleteNum(args,start){ return args.length < 2 ? len - start : args[1] > 0 ? Math.min(args[1],len - start) : 0 } function sliceArray(arr,separator){ let arr1 = [],arr2 = [] for(let i = 0;i < arr.length;i++){ i < separator ? arr1.push(arr[i]) : arr2.push(arr[i]) } // 清空原數組 arr.length = 0 return [arr1,arr2] } // 若是有傳參數 if(args.length > 0){ // 肯定 start 和 deleteNum 的取值 let start = computeStart(args[0]) let deleteNum = computeDeleteNum(args,start) // 若是 start 已經大於等於數組長度,則只需關注是否有添加元素,無需進行後續操做 if(start >= len){ if(args.length > 2){ for(let i = 2;i < args.length;i++){ arr.push(args[i]) } } } else { // 以 start 爲界分割原數組 let [arr1,arr2] = sliceArray(arr,start) // 若是有須要,就刪除元素 if(deleteNum != 0){ for(let i = 0;i < deleteNum;i++){ // 把刪除的元素放進返回的 res 數組中 res.push(arr2.shift()) } } // 若是有須要,就添加元素 if(args.length > 2){ for(let i = 2;i < args.length;i++){ arr1.push(args[i]) } } const tempArr = [...arr1,...arr2] for(let el of tempArr){ arr.push(el) } } } return res }
PS:我的感受 splice 的實現算是這幾個裏比較麻煩的,由於須要考慮不少狀況。上面的代碼已經經過 MDN 的所有測試用例,但還有很多須要優化的地方。
用某個值替換(填充)數組中的所有值或者部分值:
toFill
,begin
和 end
。toFill
表示填充元素,不傳則爲 undefined;begin
表示開始填充位置,默認從數組第一個元素開始;end
表示結束填充位置(該位置不填充),默認等於數組長度begin
能夠是正數或者負數。若是是負數且絕對值小於數組長度,則將其與長度相加做爲 begin
,若大於數組長度,則取 0 做爲 begin
end
能夠是正數或者負數,若是是正數且超過數組長度,則取數組長度做爲 begin
;若是是負數且絕對值小於數組長度,則將其與長度相加做爲 end
const arr = [0,0,0,0,0] arr.fill(5) // [5,5,5,5,5] arr.fill(5,2) // [0,0,5,5,5] arr.fill(5,2,4) // [0,0,5,5,0] arr.fill(5,-3,-1) // [0,0,5,5,0] 負值索引 => 負值索引 + 數組長度 arr.fill(5,-100,-90) // 越界,無效 arr.fill(5,100,90) // 越界,無效 arr.fill(5,4,2) // 反向,無效
Array.prototype.myFill = function(toFill,begin = 0,end = this.length){ let arr = this let len = arr.length begin = begin >= 0 ? begin : Math.abs(begin) < len ? begin + len : 0 end = end >= 0 ? Math.min(end,len) : Math.abs(end) < len ? end + len : end for(;begin < end;begin++){ arr[begin] = toFill } return arr }
複製數組的某個部分,頂替數組中的某些元素:
target
表示開始操做的位置,begin
和 end
共同決定須要複製的範圍(不包括 end
)target
開始的元素const arr = [0,1,2,3,4,5,6,7,8] arr.copyWithin(4) // [0,1,2,3,0,1,2,3,4] 缺省範圍爲整個數組 arr.copyWithin(4,7) // [0,1,2,3,7,8,6,7,8] arr.copyWithin(4,6,9) // [0,1,2,3,6,7,8,7,8] // 對於負值索引、反向索引和越界索引的處理,和 fill 方法相似
Array.prototype.myCopyWithin = function(target = 0,begin = 0,end = this.length){ let arr = this let len = arr.length let copyArr = [] let m = 0,n = 0 target = target >= 0 ? target : Math.abs(target) < len ? target + len : 0 begin = begin >= 0 ? begin : Math.abs(begin) < len ? begin + len : 0 end = end >= 0 ? Math.min(end,len) : Math.abs(end) < len ? end + len : end // 把須要複製的元素放到 copyArr 數組中 for(;begin < end;begin++){ copyArr[m++] = arr[begin] } let _len = copyArr.length < len - target ? target + copyArr.length : len // 用 copyArr 數組從 target 開始覆蓋原數組 for(;target < _len;target++){ arr[target] = copyArr[n++] } return arr }
對於基本類型的包裝對象來講,調用該方法會返回對應的基本類型值,但對於數組通常會直接返回數組自己
const arr = [1,2,3] arr.valueOf() === arr
Array.prototype.myValueOf = function(){ return this }
將數組中的每一個元素轉爲字符串並用規定好的分隔符進行鏈接:
undefined
,而 undefined
和 null
會進一步被轉化爲空字符串。[1,2,3].join() // "1,2,3" 缺省是逗號做爲鏈接符 [1,2,3].join('.') // "1.2.3" [{},{},{}].join('**') // "[object Object]**[object Object]**[object Object]"
Array.prototype.myJoin = function(connector = ','){ let arr = this let str = '' for(x of arr){ x = typeof(x) === 'undefined' || x === null ? "" : x str += x.toString() + connector } return str.slice(0,str.length - connector.length) } // 或者 Array.prototype.myJoin = function(connector = ','){ let arr = this let len = arr.length let str = '' for(let i = 0;i < len;i++){ arr[i] = typeof(arr[i]) === 'undefined' || arr[i] === null ? "" : arr[i] // 若是是最後一個元素,則不加鏈接符(後綴符) str += arr[i].toString + (i === len - 1 ? '' : connector) } return str }
toString 能夠看做是 join 的一種特殊狀況,即傳入的分隔符是逗號,其它的都同樣(包括對 undefined
、null
和 empty 元素的處理)
[1,2,3].toString() // "1,2,3" [{a:1},{b:2}].toString() // "[obejct Object],[object Object]"
Array.prototype.myToString = function(){ let arr = this let str = "" for(x of arr){ x = typeof(x) === 'undefined' || x === null ? "" : x str += `${x.toString()},` } return str.slice(0,str.length - 1) }
concat 能夠用於合併數組
[Symbol.isConcatSpreadable]=true
,此時會取出這個對象的每一項(除了 length
)放入新數組[Symbol.isConcatSpreadable]=false
Array.prototype.myConcat = function(...args){ let arr = this let res = [] let k = 0 const isArrayLike = obj => { if( typeof o === 'object' && isFinite(o.length) && o.length >= 0 && o.length === Math.floor(o.length) && o.length < 4294967296) return true else return false } for(let el of arr){ res[k++] = el } for(let el of args){ // 若是是數組且沒有禁止展開 if(Array.isArray(el) && el[Symbol.isConcatSpreadable] != false){ for(let _el of el){ res[k++] = _el } } else { // 若是是類數組且容許展開 if(isArrayLike(el) && el[Symbol.isConcatSpreadable]){ for(let key in el){ // 把除了 length 以外的鍵值都放入新數組中 if(key !== 'length'){ res[k++] = el[key] } } } else { res[k++] = y } } } return res }
PS:這裏檢測類數組對象的方式可能不太嚴謹,且沒有考慮 empty 元素的狀況
at 是一個比較新的方法,目前瀏覽器尚未實現:
相比 arr[2]
,這個方法的優點在哪裏呢?優點在於能夠很方便地訪問那些數組末尾的元素,好比如今要訪問 const arr = [1,2,3,4]
的倒數第二個元素,再也不須要使用 arr[arr.length - 2]
,只須要 arr.at(-2)
。
const arr = [1,2,3,4] arr.at(2) // 3 arr.at(-1) // 4
Array.prototype.myAt = function(searchIndex){ let arr = this let len = arr.length let searchIndex = searchIndex >= 0 ? searchIndex : Math.abs(searchIndex) < len ? searchIndex + len : Infinity return arr[searchIndex] }
const arr = ['a','b','c','d','a','e'] arr.indexOf('b') // 從前日後查找'b',返回它的索引1 arr,indexOf('b',2) // 從索引2開始,從前日後查找'b',找不到,返回 -1 arr.lastIndexOf('a') // 從後往前查找'a',返回它的索引4 arr.lastIndexOf('a',2) // 從索引2開始,從後往前查找'a',返回它的索引0 arr.includes('c') // 數組存在'c',返回 true arr.includes('c',3) // 從索引3開始,數組不存在'c',返回 false arr.includes('c',300) // 超出數組長度,返回 false arr.includes('c',-2) // 負值=>負值+數組長度=>4,從索引4開始查找,返回 false arr.includes('c',-100) // 負值=>負值+數組長度=>-94,從頭開始查找,返回 true
Array.prototype.myIndexOf = function(target,start = 0){ let arr = this let len = arr.length let _start = start >= 0 ? start : Math.abs(start)<= len ? len + start : 0 for(;_start < len;_start++){ if(arr[_start] === target){ return _start } } return -1 }
lastIndexOf 和 indexOf 相比,有些地方是反過來的:
const arr = [1,2,3,2,5] arr.lastIndexof(2) // 3
Array.prototype.myLastIndexOf = function(target,start){ let arr = this let len = arr.length start = start || arr[arr.length - 1] let _start = start < 0 ? len + start : start >= len ? arr.length - 1 : start for(;_start > 0;_start--){ if(arr[_start] === target){ return _start } } return -1 }
inlcudes 和 indexOf 相似,可是返回的是布爾值。
爲何有了 indexOf
還要引入 inlcudes
?一是由於返回布爾值,語義更加清晰;二是由於 includes
內部使用的是相似 Object.is
的比較方式,而非 indexOf 所使用的 ===
,因此能夠準確判斷 NaN。
[1,NaN].indexOf(NaN) // -1 [1,NaN],includes(NaN) // true // 然而,inlcudes 仍然沒法準確判斷±0,會認爲二者相等 [1,+0].includes(-0) // true [1,0].includes(+0) // true
Array.prototype.myIncludes = function(target,start = 0){ let arr = this let len = arr.length let _start = start >=0 ? start : Math.abs(start) <= len ? start + len : 0 function isSame(x,y){ return x === y || typeof(x)=='number'&&typeof(y)=='number'&&isNaN(x)&&isNaN(y) // return x === y || x!=x && y!= y // return x === y || Number.isNaN(x) && Number.isNaN(y) } for(;_start < len;_start++){ if(isSame(arr[_start],target)){ return true } } return false }
這裏判斷 NaN 的方式不少,一種是直接利用最準確的 Number.isNaN
,一種是使用 isNaN
,但要保證參數是數字,還有一種是利用 NaN 自身的特性,即「本身不等於本身」。
slice 用於產生數組切片:
begin
和 end
,表示開始位置和結束位置;能夠只接受一個參數 begin
,表示開始位置;能夠不接受任何參數,則缺省開始位置爲第一個元素,結束位置爲最後一個元素begin
能夠是正數或者負數:
end
能夠是正數或者負數:
begin
可能大於 end
,此時就直接返回一個空數組const arr = [1,2,3,4,5] arr.slice(1) // [2,3,4,5] arr.slice(1,4) // [2,3,4] arr.slice(-4,-1) // [2,3,4] 負值 => 數組長度加負值 arr.slice(4,1) // [] 反向索引,返回空數組
// 經過默認參數值,爲 begin 和 end 設置缺省值 Array.prototype.mySlice = function(begin = 0,end = this.length){ let arr = this let len = arr.length let res = [] let k = 0 begin = begin >= 0 ? begin : Math.abs(begin) <= len ? begin + len : 0 end = end < 0 ? end + len : Math.min(end,len) for(;begin < end;begin++){ res[k++] = arr[begin] } return res }
用於數組扁平化(數組降維):
Infinity
能夠直接將數組轉化爲一維數組flat
的實現能夠參考數組扁平化的方法,但它實現起來須要更加靈活,能夠傳參控制降維次數[1,[2,3],[[4,5],6]].flat() // [1,2,3,[4,5],6] [1,[2,3],[[4,5],6]].flat(2) // [1,2,3,4,5,6]
1)reduce + 遞歸
Array.prototype.myFlat = function(times = 1){ let arr = this // 若是參數沒法轉化爲數字,或小於等於0,則直接將原數組返回 if(!Number(times) || Number(times) <= 0){ return arr } return arr.reduce((acc,cur) => { return acc.concat(Array.isArray(cur) ? cur.myFlat(times - 1) : cur) },[]) }
2)forEach + 遞歸
Array.prototype.myFlat = function(times = 1){ let arr = this let res = [] if(!Number(times) || Number(times) <= 0){ return arr } arr.forEach(el => { res.concat(Array.isArray(el) ? el.myFlat(times - 1) : el) }) return res }
3)for 循環 + in 檢查 + 遞歸
Array.prototype.myFlat = function(times = 1){ let arr = this let res = [] if(!Number(times) || Number(times) <= 0){ return arr } for(let i = 0;i < arr.length;i++){ if(i in arr){ if(Array.isArray(arr[i])){ res = [...res,...arr[i].myFlat(times - 1)] } else { res = [...res,arr[i]] } } } return res }
能夠遍歷數組的每一個元素,執行特定的操做:
// res 爲 undefined const res = ['a','b','c'].forEach(function(item,index,arr){ console.log(`${index}-${item}`,arr,this) return 123 },{}) // 遍歷過程當中即便push了新元素,也仍然按照原數組長度進行遍歷。打印 1,2 [1,2].forEach((item,index,arr) => { arr.push(3,4,5) console.log(item) }) // 遍歷過程當中能夠提早修改未遍歷元素。打印 1,100 [1,2].forEach((item,index,arr) => { arr[1] = '100' console.log(item) }) // 遍歷過程當中能夠 return,但只是結束當前此次遍歷,沒法跳出整個 forEach。打印 2 [1,2].forEach((item,index,arr) => { if(index == 0) return console.log(item) }) // 遍歷過程當中會自動跳過 empty 元素(null 和 undefined 不會跳過)。打印 1,2,4 [1,2,,4].forEach((item,index,arr) => { console.log(item) })
Array.prototype.myforEach = function (fn,thisArg = null){ if(typeof fn != 'function'){ throw new TypeError(`${fn} is not a function`) } let arr = this for(let i = 0,len = arr.length;i < len;i++){ // 若是不是 empty 元素 if(i in arr){ fn.call(thisArg,arr[i],i,arr) } } }
將原數組的每一個元素映射爲執行回調函數以後的返回值:
forEach
差很少,也是會跳過 empty 元素// 返回新數組 [2,4,6,8] [1,2,3,4].map((item,index) => item * 2)
Array.prototype.myMap = function(fn,thisArg = null){ if(typeof fn != 'function'){ throw TypeError(`${fn} is not a function`) } let arr = this // 這裏不要使用 let newArr = [],不然修改原數組長度時會影響新數組長度 let newArr = new Array(arr.length) for(let i = 0,len = arr.length;i < len;i++){ if(i in arr){ const res = fn.call(thisArg,arr[i],i,arr) newArr[i] = res } } return newArr }
flatMap
至關因而 map
和 flat(1)
的結合。它會給某個數組調用 map
方法,若是獲得了一個多維數組,則會對該數組進行一次降維。
PS:注意這個方法不會改變原數組。
const arr1 = [1, 2, 3, 4]; arr1.map(x => [x * 2]); // [[2], [4], [6], [8]] arr1.flatMap(x => [x * 2]); // [2, 4, 6, 8]
咱們能夠在每次執行 flatMap
的回調並返回一個新結果時,判斷該結果是否是數組,若是是則取出數組中的每一個元素放入最終返回的新數組中。
Array.prototype.myFlatMap = function(fn,thisArg = null){ if(typeof fn != 'function'){ throw new TypeError(`${fn} is not a function`) } let arr = this let newArr = new Array(arr.length) let k = 0 for(let i = 0;i < arr.length;i++){ if(i in arr){ const res = fn.call(thisArg,arr[i],i,arr) if(Array.isArray(res)){ for(let el of res){ newArr[k++] = el } } else { newArr[k++] = res } } } return newArr }
find 返回數組中第一個符合條件的元素或者 undefined:
indexOf
搜索數組,沒法自定義搜索條件,因此出現了 find
find
會對每一個元素執行一次回調函數,直到找到符合條件的元素,就將這個元素返回(永遠只返回一個),並結束函數執行;找不到則返回 undefinedi in arr
的檢查[1,2,3,4,5].find((item,index,arr) => item > 2) // 3
Array.prototype.myFind = function(fn,thisArg = null){ if(typeof fn != 'function'){ throw new TypeError(`${fn} is not a function`) } let arr = this for(let i = 0;i < arr.length;i++){ const result = fn.call(thisArg,arr[i],i,arr) if(result){ return arr[i] } } return undefined }
和 find
基本一致,可是 findIndex 返回的是第一個符合條件的元素的索引,沒有這樣的元素就返回 -1
[1,2,3,4,5].findIndex((item,index) => item > 2) // 2
Array.prototype.myfindIndex = function(fn,thisArg = null){ if(typeof fn != 'function'){ throw new TypeError(`${fn} is not a function`) } let arr = this for(let i = 0;i < arr.length;i++){ const result = fn.call(thisArg,arr[i],i,arr) if(result){ return i } } return -1 }
經過 find
搜索數組,只能找到一個符合條件的元素,而 filter
能夠篩選出全部符合條件的元素:
[1,2,3,4,5].filter((item,index) => item > 2) // 返回 [3,4,5] [1,2,3,4,5].filter((item,index) => item > 100) // 沒有符合的元素,返回 []
Array.prototype.myFilter = function(fn,thisArg = null){ if(typeof fn != 'function'){ throw new TypeError(`${fn} is not a function`) } let arr = this let res = [] let k = 0 for(let i = 0;i < arr.length;i++){ if(i in arr){ const result = fn.call(thisArg,arr[i],i,arr) // 若是元素符合條件,則放入新數組中 if(result){ res[k++] = arr[i] } } } }
接受一個回調函數表示判斷條件,只要數組中有一個元素知足該條件(回調函數返回 true),some
方法就返回 true,不然返回 false
[1,2,3,4].some((item,index) => item>3) // 至少有一個大於3的數,返回 true [1,2,3,4].some((item,index) => item>100) // 沒有一個大於100的數,返回 false
Array.prototype.mySome = function(fn,thisArg = null){ if(typeof fn != 'function'){ throw new TypeError(`${fn} is not a function`) } let arr = this for(let i = 0;i < arr.length;i++){ const result = fn.call(thisArg,arr[i],i,arr) if(result){ return true } } return false }
接受一個回調函數表示判斷條件,只有數組中全部元素都知足該條件(回調函數返回 true),every
方法纔會返回 true,有一個不知足都會返回 false
[1,2,3,4].every((item,index) => item>0) // 全部元素都大於0,返回 true [1,2,3,4].every((item,index) => item>3) // 並不是全部元素都大於3,返回 false
Array.prototype.myEvery = function(fn,thisArg = null){ if(typeof fn != 'function'){ throw new TypeError(`${fn} is not a function`) } let arr = this for(let i = 0;i < arr.length;i++){ const result = fn.call(thisArg,arr[i],i,arr) if(!result){ return false } } return true }
reduce
能夠歸併數組的每一個元素,最終構建一個累計歸併值做爲返回值:
arr.reduce((acc,cur,index,arr) => {...},baseAcc)
acc
,也即累計歸併值。初始累計歸併值缺省是第一個非 empty 元素,且此時會從該元素的下一個元素開始迭代。// 沒有提供初始累計歸併值,所以缺省是1,而且從2開始迭代 [1,2,3,4].reduce((acc,cur) => acc + cur) // 10 // 提供100做爲初始累計歸併值,從1開始迭代 [1,2,3,4].reduce((acc,cur) => acc + cur,100) // 110 // 二維數組轉化爲一維數組,concat 自己會拍平一維數組 [1,2,[3,4]].reduce((acc,cur) => acc.concat(cur),[]) // [1,2,3,4]
實現的時候,有兩個關鍵的地方:
typeof baseAcc === 'undefined'
判斷不許確,由於有可能傳的第二個參數確實就是 undefined,這裏能夠經過剩餘參數的長度判斷Array.prototype.myReduce = function(...args){ let fn = args[0] let arr = this let len = arr.length let index = 0,acc if(typeof fn != 'function'){ throw new TypeError(`${fn} is not a function`) } // 若是傳了第二個參數 if(args.length >= 2){ acc = args[1] } else { // 只要當前數組還沒找到非 empty 元素,就一直遍歷下去 while(index < len && !(index in arr)){ index++ } // 若是數組是一個充滿 empty 元素的空數組,則拋出錯誤 if(index >= len){ throw new TypeError('Reduce of empty array with no initial value') } // index 加一,表示第一個非 empty 元素的下一個元素 acc = arr[index++] for(;index < len;index++){ if(index in arr){ acc = fn(acc,arr[index],index,arr) } } return acc } }
用法基本和 reduce
一致,區別是它是從後往前去遍歷數組的。
[0, 1, 2, 3].reduceRight((acc, cur) => { console.log(cur); }); // 2 // 1 // 0
reduceRight
的實現和 reduce
基本同樣,但須要注意:非 empty 元素的查找以及數組的遍歷順序是反過來的
Array.prototype.myReduceRight = function(...args){ let fn = args[0] let arr = this let len = arr.length let index = len - 1,acc if(typeof fn != 'function'){ throw new TypeError(`${fn} is not function`) } if(args.length >= 2){ acc = args[1] } else { while(index > 0 && !(index in arr)){ index-- } if(index == 0){ throw new TypeError('Reduce of empty array with no initical value') } acc = arr[index--] for(;index >= 0;index--){ if(index in arr){ acc = fn(acc,arr[index],index,arr) } } return acc } }
判斷傳入的參數是否是數組
Array.isArray([1,2,3]) // true
這裏能夠直接借用 Object.prototype.toString
判斷數據類型
Object.defineProperty(Array,"myIsArray",{ value: function(arr){ return Object.prototype.toString.call(arr) === '[object Array]' } })
基於一個傳進來的類數組對象或者可迭代對象,淺拷貝生成一個新數組。若是指定了第二個參數爲回調函數,則會爲新數組的每一個元素執行一次該回調函數
const set = new Set(['foo', 'bar', 'baz', 'foo']); Array.from(set); // [ "foo", "bar", "baz" ]
Object.defineProperty(Array,"myFrom",{ value:function(toChange,fn,thisArg = null){ let res = [...toChange] if(typeof fn === 'function'){ for(let i = 0;i < res.length;i++){ res[i] = fn.call(thisArg,res[i],i,res) } } return res } })
接受多個參數,全部參數都會成爲新建立的數組的元素
Array.of(7); // [7] Array.of(1, 2, 3); // [1, 2, 3]
Object.defineProperty(Array,"myOf",{ value: function(){ let res = [] for(let i = 0;i < arguments.length;i++){ res[i] = arguments[i] } return res } })
以上就是本文的所有內容,感謝閱讀。