共计 18605 个字符,预计需要花费 47 分钟才能阅读完成。
这是 JS 原生办法原理探索系列的第六篇文章,这次咱们来实现数组的 30 个 API。在开始之前,能够先看一下本文的思维导图:
文章分为四个局部,别离介绍在数组原生 API 中,会批改原数组的办法、不会批改原数组的办法、用于遍历的办法以及静态方法,共计 30 个。在讲每个办法的具体实现之前,会简要介绍它们的用法(更具体的查阅 MDN 即可),之后给出实现的思路和具体的代码。代码来源于本人思考以及对 polyfill 的参考,实测能够通过大部分测试用例,但不排除有更好的思路以及值得优化的中央,若发现任何谬误或者值得改良之处,欢送评论区留言斧正。
这是一些实现时须要留神的中央:
- 原型办法挂载在数组原型上。因为办法是通过数组实例调用的,所以咱们能够在办法外部通过 this 拿到调用者,也就是数组(如果怕批改到原数组,能够把这个 this 浅拷贝一份)
- 大部分办法在遍历数组的时候,会跳过 empty 元素(空位),而有的办法却不会。因而,在遍历数组的过程中,要留神判断元素是不是 empty 元素 —— 能够用 in 判断,比方索引 1 的元素不是 empty,那么 1 in arr 是会返回 true 的。此外,也能够抉择用
for…in
遍历数组,它只会遍历出那些非 empty 元素的索引(留神:for...of
能够遍历出 empty 元素,遍历出的后果是undefined
)
上面注释开始。
会扭转数组的办法
pop
用法
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
用法
push 办法能够往数组开端增加任意多个元素,并将数组长度作为返回值:
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
用法
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
用法
unshift 办法能够往数组头部增加任意多个元素,并将数组长度作为返回值:
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
用法
reverse 将原数组进行反转,最终返回原数组
[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
}
sort
用法
reverse 也算是一种排序办法,但显然它不够灵便,于是有了 sort
办法。
- sort 不承受参数或者承受的参数为
undefined
时,默认的排序规定是:将每个元素转化为字符串,再将它们依照 Unicode 编码从小到大排序。其中,null
会转化为"null"
,undefined
固定排在数组最初 - sort 承受参数且为排序函数的时候,依照排序函数的规定排序:若函数返回值为正数,则第一个参数排在第二个参数后面,若为负数,则在它前面,若为 0 则地位不变
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
用法
splice 能够做三种事:删除元素、增加元素、替换元素。
- 承受三个参数:开始操作的地位
start
、删除的元素个数num
,以及增加的元素item1、item2、...
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 的全副测试用例,但还有不少须要优化的中央。
fill
用法
用某个值替换(填充)数组中的全副值或者局部值:
- 承受三个参数:
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
}
copyWithin
用法
复制数组的某个局部,顶替数组中的某些元素:
- 承受三个参数,
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
}
不会扭转数组的办法
valueOf
用法
对于根本类型的包装对象来说,调用该办法会返回对应的根本类型值,但对于数组个别会间接返回数组自身
const arr = [1,2,3]
arr.valueOf() === arr
实现
Array.prototype.myValueOf = function(){return this}
join
用法
将数组中的每个元素转为字符串并用规定好的分隔符进行连贯:
- 别离对数组每个元素调用一次 toString,之后将这些后果用传给 join 的参数连接起来,返回一个字符串。
- 如果有 empty 元素,则会被当作
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
用法
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
用法
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
用法
at 是一个比拟新的办法,目前浏览器还没有实现:
- 该办法承受一个整数作为参数,并返回数组对应索引的元素。
- 如果参数是正数且绝对值小于数组长度,则将其与数组长度相加作为须要查找的索引。
- 如果没有合乎索引的元素,则返回 undefined
相比 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]
}
indexOf
用法
- 承受两个参数,第一个参数示意查找指标,第二个参数示意开始查找地位
- 第二个参数能够是负数或者正数,负数超出数组索引间接返回 -1,正数与数组长度相加后若是负数则作为开始查找地位,若是正数则从 0 开始查找
- 找到就返回元素索引,否则返回 -1
- 采纳严格相等去匹配数组元素
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
用法
lastIndexOf 和 indexOf 相比,有些中央是反过来的:
- 始终都是从后往前查找
- 第二个参数能够是负数或者正数,负数超出数组索引则从最开端开始查找,正数与数组长度相加后若是负数则作为开始查找地位,若是正数则间接返回 -1
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
}
includes
用法
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
用法
slice 用于产生数组切片:
- 能够承受两个参数
begin
和end
,示意开始地位和完结地位;能够只承受一个参数begin
,示意开始地位;能够不承受任何参数,则缺省开始地位为第一个元素,完结地位为最初一个元素 -
begin
能够是负数或者正数:- 如果是负数,间接取本身;
- 如果是正数,且正数绝对值不超过数组长度,则将其与数组长度相加,若超过数组长度,则取 0
-
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
}
flat
用法
用于数组扁平化(数组降维):
- 传入的参数代表对于数组中的每一个元素,要降维多少次,默认为 1 次,传入
Infinity
能够间接将数组转化为一维数组 - flat 自身会跳过 empty 元素,因而这里遍历数组的时候须要进行查看。要么是应用后面那样的 for 循环 + in 手动查看 empty 元素,要么是应用自身就能够跳过 empty 元素的数组遍历办法(比方 reduce 或者 forEach 等)
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
}
用于遍历的办法
forEach
用法
能够遍历数组的每个元素,执行特定的操作:
- 承受两个参数,一个回调函数和一个缺省为 null 的 thisArg。会遍历数组中所有元素,对每个元素执行该函数;
- 不会新建数组也不会批改原数组,总是返回 undefined(即便明确指定了返回值)
- 遍历范畴在执行回调前就确定了,遍历过程中 push 会批改原数组,然而不会影响遍历范畴
- 遍历过程中能够提前批改前面才会遍历到的元素
- 会间接跳过 empty 元素
// 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)
}
}
}
map
用法
将原数组的每个元素映射为执行回调函数之后的 返回值:
- 根本实现和
forEach
差不多,也是会跳过 empty 元素 - forEach 的遍历范畴一开始就确定好,所以须要先保留最后数组长度;同理,map 最终返回的新数组长度也是一开始就与原数组长度绑定好了,申明新数组的时候须要指定这个长度
// 返回新数组 [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
用法
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
用法
find 返回数组中第一个符合条件的元素或者 undefined:
- 通过
indexOf
搜寻数组,无奈自定义搜寻条件,所以呈现了find
find
会对每个元素执行一次回调函数,直到找到符合条件的元素,就将这个元素返回(永远只返回一个),并完结函数执行;找不到则返回 undefined- 留神这个办法不会跳过 empty 元素,所以这里不做
i 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
}
findIndex
用法
和 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
}
filter
用法
通过 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]
}
}
}
}
some
用法
承受一个回调函数示意判断条件,只有数组中有一个元素满足该条件(回调函数返回 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
}
every
用法
承受一个回调函数示意判断条件,只有数组中所有元素都满足该条件(回调函数返回 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
用法
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,这里能够通过残余参数的长度判断 - 如何找出数组的第一个非 empty 元素?遍历数组,用 in 判断
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
}
}
reduceRight
用法
用法根本和 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
用法
判断传入的参数是不是数组
Array.isArray([1,2,3]) // true
实现
这里能够间接借用 Object.prototype.toString
判断数据类型
Object.defineProperty(Array,"myIsArray",{value: function(arr){return Object.prototype.toString.call(arr) === '[object Array]'
}
})
Array.from
用法
基于一个传进来的类数组对象或者可迭代对象,浅拷贝生成一个新数组。如果指定了第二个参数为回调函数,则会为新数组的每个元素执行一次该回调函数
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
用法
承受多个参数,所有参数都会成为新创建的数组的元素
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
}
})
以上就是本文的全部内容,感激浏览。