这是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 } })
以上就是本文的所有内容,感谢阅读。