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


    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
    }
    
  • 相关阅读:
    hadoop_虚拟机linux环境部署全教程
    [科普文] Web3 中的资产负债表
    Nacos 安装教程(史上最详细保姆级教程)
    高级网络调试技巧:使用Charles Proxy捕获和修改HTTP/HTTPS请求
    为什么推荐 Java 开发人员都学习并使用 Kotlin?
    备战秋招--redis篇
    Element-Ui el-table 动态添加行
    VSCode使用简介
    # Windows 环境下载 Android 12源码
    从《职业分类大典》看人才需求,优秀的程序员应该具备哪些能力?
  • 原文地址:https://blog.csdn.net/qq_45272642/article/details/128180600