跳转到内容

数组方法手写原理

刀刀

3/20/2025

0 字

0 分钟

forEach

forEach 主要是做循环遍历操作,接收一个回调函数,该回调函数传递三个参数:

  • 参数1:该遍历项的每一项数据(即 item
  • 参数2:该项的索引(即 index
  • 参数3:该数组
js
Array.prototype.my_foreach = function(callback) {
    for(let i = 0; i < this.length; i++) {
        callback(this[i], i, this)
    }
}

其中,这里的 this 指向的是使用该方法的数组(如下方示例中的 daodao 数组),而 for 循环需要获取到数组的长度,因此用到了 this.lengh ,每一项的数据则是 this[i]

下面使用一下自己封装好的 my_foreach 方法:

js
daodao.my_foreach((item, index, arr) => {
  console.log(item, index, arr)
})

打印结果如下所示:

p9HbM2F.png

拓展

使用过 forEach 的程序员都知道 return 无法终止 forEach 循环,现在知道其原理了,因为我们在使用的 forEach 里写的 return 是在回调函数 callback 中,return 只能跳出当前的循环,无法跳出整体的循环。

map

map 主要做某种操作,并返回一个新的数组,接收一个回调函数,该回调函数传递三个参数:

  • 参数1:该遍历项的每一项数据(即 item
  • 参数2:该项的索引(即 index
  • 参数3:该数组
js
Array.prototype.my_map = function(callback) {
    const arr = []
    for(let i = 0; i < this.length; i++) {
        arr.push(callback(this[i], i, this)) // 此时拿到的是数组的每一条数据
    }
    return arr
}

由于要返回一个新数组,因此需要在循环体外先定义一个空数组,每一次循环都往数组内 push 一次数据,最后再 return 返回。

js
const newdaodao = daodao.my_map((item, index) => {
  return {
    ...item,
    name: item.name + ',你好'
  }
})
console.log(newdaodao);

最后运行结果如下所示:

js
[
    {
        "age": 20,
        "name": "刀刀,你好"
    },
    {
        "age": 21,
        "name": "小刀,你好"
    },
    {
        "age": 22,
        "name": "杜一刀,你好"
    }
]

filter

filter 每一遍历一次回调函数都会返回一个布尔值,如果该值为真才添加到数组中,为假则不添加,接收一个回调函数,该回调函数传递三个参数:

  • 参数1:该遍历项的每一项数据(即 item
  • 参数2:该项的索引(即 index
  • 参数3:该数组
js
Array.prototype.my_filter = function(callback) {
    const arr = []
    for(let i = 0; i < this.length; i++) {
        // callback(this[i], i, this) && arr.push(callback(this[i], i, this)) // 这么写的话最终返回的结果是:(2) [true, true].因为此时拿到的是一个布尔值
        callback(this[i], i, this) && arr.push(this[i])
    }
    return arr
}

使用:

js
const newdaodao = daodao.my_filter((item, index) => item.name.length <= 2)
console.log(newdaodao);

结果返回:

js
[
    {
        "age": 20,
        "name": "刀刀"
    },
    {
        "age": 21,
        "name": "小刀"
    }
]

every

遍历所有项并判断是否匹配要求,如果都匹配返回 true,只要有一个不符合要求返回 false。

实现思路与 filter 类似,回调函数返回的是一个判断后的布尔值,遍历后判断其是否为真,只要有一项为假,就返回假。都不为假才返回真。

js
Array.prototype.my_every = function(callback) {
    for(let i = 0; i < this.length; i++) {
        // callback(this[i], i, this) // 这里拿到的是一个布尔值
        if(!callback(this[i], i, this)) return false
    }
    return true
}

使用:

js
const newdaodao1 = daodao.my_every((item, index) => item.name.length <= 3)
const newdaodao2 = daodao.my_every((item, index) => item.name.length <= 2)
console.log(newdaodao1); // true
console.log(newdaodao2); // false

some

someevery 相反,some 只需要有一个匹配就能返回 true,只有在全部都不匹配的情况下才返回 false。因此只需要把上方 every 的代码稍微调整一下即可。

js
Array.prototype.my_some = function(callback) {
    for(let i = 0; i < this.length; i++) {
        // callback(this[i], i, this) // 这里拿到的是一个布尔值
        if(callback(this[i], i, this)) return true
    }
    return false
}

find

find 主要用于寻找符合要求的第一项数组数据,如果遍历完数组都没有找到匹配数据,则返回 undefined。与 some、every、filter 一样,回调函数拿到的是一个判断布尔值,因此同样使用 if 来判断,如果符合要求则返回该项的数据。

js
Array.prototype.my_find = function(callback) {
    for(let i = 0; i < this.length; i++) {
        // callback(this[i], i, this) // 这里拿到的是一个布尔值
        if(callback(this[i], i, this)) return this[i] // 返回这一项
    }
    return undefined
}

findIndex

与 find 相同,判断逻辑上一样,区别是 findIndex 返回的是索引,因此修改一下返回的值为索引即可。如果都找不到数据,则返回 -1 。

js
Array.prototype.my_find = function(callback) {
    for(let i = 0; i < this.length; i++) {
        // callback(this[i], i, this) // 这里拿到的是一个布尔值
        if(callback(this[i], i, this)) return i // 返回这一项的索引值i
    }
    return -1
}

reduce

js
Array.prototype.my_reduce = function(callback, ...args) {
    let start = 0, pre
    if(args.length) {
        pre = args[0]
    } else {
        pre = this[0]
        start = 1
    }
    
    for(let i = start; i < this.length; i++) {
        pre = callback(pre, this[i], i, this)
    }
    
    return pre
}

reduce 函数接收两个参数,一个是回调函数 callback ,另一个是起始值 args 。其中,回调函数执行后又返回四个参数:

  • 参数1:当前计算累加的值。判断用户是否传递了初始值,传递了就赋值给参数1,没传递则获取数组的第一项数据(在循环体外部做判断)
  • 参数2:遍历的第 i 项数据
  • 参数3:索引
  • 参数4:整个数组

注意,如果使用者没有传初始值,则参数1获取的是数组第一项,此时累加则是从数组第二项开始循环遍历,因此 for 循环中 i 的起始值需要动态判断(上方示例代码中 strat 的作用正是如此)。

最后把累加的值返回即可。

fill

js
Array.prototype.my_fill = function(init, start = 0, end) {
    end = end < 0 ? this.length + end : end
    for(let i = 0; i < end; i++) {
        this[i] = value
    }
    return this
}

includes

js
Array.prototype.my_includes = function(value, start) {
    start = start < 0 ? this.length + start : start
    for(let i = 0; i < this.length; i++) {
        // 判断当前的值是否等于要比较的值,相等返回true
        if(this[i] === value) return true
    }
    return false
}

join

js
Array.prototype.my_join = function(s = ',') {
    let str = ''
    for(let i = 0; i < this.length; i++) {
        strr = i === 0 ? `${str}${this[i]}` : `${str}${s}${this[i]}` // 判断当前是否为第一项,第一项则不添加分隔符
    }
    return str
}

flat

js
Array.prototype.my_flat = function(num = Infinity) {
    let arr = this
    let i = 0
    while(arr.some(item => Array.isArray(item))) {
        arr = [].concat(...arr)
        i++
        if(i >= num) break
    }
    return arr
}