手写数组方法之不改变原数组方法

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 的一种特殊情况,即传入的分隔符是逗号,其它的都一样(包括对 undefinednull 和 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
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值