当前位置: 首页 > news >正文

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

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
}

相关文章:

  • C/C++实现高性能并行计算——1.pthreads并行编程(中)
  • 【分享】如何将word格式文档转化为PDF格式
  • Linux操作系统预备 —— 冯·诺伊曼体系结构
  • 【MATLAB源码-第201期】基于matlab的黏菌群优化算法(SMA)无人机三维路径规划,输出做短路径图和适应度曲线
  • Jammy@Jetson Orin Nano - Tensorflow GPU版本安装
  • 最新springboot家乡特色推荐系统
  • 【蓝桥杯】快读|min和max值的设置|小明和完美序列|​顺子日期​|星期计算|山
  • 深度学习手写字符识别:推理过程
  • 自动驾驶中的障碍物时间对齐法
  • C语言中strstr函数的使用!
  • 动态规划-最长公共子串(c)
  • 前端路由与后端路由的区别
  • Mockito verify Junit5集成 Mockito
  • 文件或者文件夹的忽略
  • 年产2万吨山楂酒工厂的设计-发酵工段及车间的设计(lunwen+任务书+cad图纸)
  • 【LIN总线测试】——LIN主节点调度表测试
  • 写一个flutter程序2
  • 给定一个数组arr,长度为N且每个值都是正数,代表N个人的体重。再给定一个正数 limit,代表一艘船的载重。
  • 【大道模式】状态模式 - State Pattern(审核状态流转)
  • 协议-序列化-http-Cookie-Session-https
  • 数据结构—Map集合
  • SpringMVC对消息转换器的处理相关
  • Linux-文件压缩解压
  • [附源码]计算机毕业设计JAVA医药管理系统
  • [附源码]计算机毕业设计基于SpringBoot+Vue的健身房会员系统的设计与实现
  • 9 特色聚类
  • python中的集合详解
  • pringboot面向爱宠人群的宠物资讯系统36as8计算机毕业设计-课程设计-期末作业-毕设程序代做
  • Flink系列之Flink中StateBackend深入剖析和应用
  • Java可变参数和集合工具类Collections的详细介绍
  • 网站构建初级教程
  • 项目管理逻辑:老板为什么赔钱的项目也做?为什么害怕你闲着?