[原]JS原生数组方法的用法及其实现

本文原载于CSDN

此文章将会介绍数组大部分的原生方法的用法,并自己实现一个具有相同功能的方法,不定期更新。顺序暂时看起来比较乱,所有方法都写完后会重新整理一下。

概述

JS原生数组方法的参数都大致符合下面的通式(针对不同的参数,会在遇到之后补充),比较常见的方法一大部分都是遍历一遍数组,然后给每个元素执行一次提供的函数,也就是回调函数,所以实现基本都是基于遍历的。其他的,会在后面详细说明。

arr.method( function (value, index, arr) {
// value : 必选,当前元素
// index : 可选,当前元素索引值
// arr : 可选,当前数组
// thisValue : 可选,替换当前的this指向
}, thisValue)

大致将数组方法分为两类,一类是改变原数组的方法,一类是不改变原数组的方法。

// 1. 改变原数组
arr.push();
arr.pop();
arr.unshift();
arr.shift();
arr.reverse();
arr.solice();
arr.sort();
arr.copyWithin();

// 2. 不改变原数组
arr.concat();
arr.toString();
arr.slice();
arr.join();
arr.split();
arr.forEach();
arr.map();
arr.filter();
arr.some();
arr.every();
arr.concat();

forEach()

forEach() 方法用于调用数组的每个元素,并将元素传递给回调函数。

forEach() 没有什么好说的,就是遍历数组的所有元素,需要注意的就是此方法不会改变原数组。使用方法如下

var arr = [1, 2, 3, 4, 5, 6, 7]
arr.forEach(function (val, index, arr) {
console.log(val)
})
// 1
// 2
// 3
// 4
// 5
// 6
// 7

实现起来也很简单,就是遍历一次数组,然后给每个元素都执行一次回调函数,直接上实现代码

Array.prototype.myForEach = function (fn, callback) {
if (typeof fn === 'function') { // 第一个参数必须是函数才能执行,否则报错
// 遍历数组
for(var i = 0, len = this.length; i < len; i ++) {
fn.call(callback, this[i], i, this) // 将this绑定到回调函数之上,并传入参数
}
}
}

// 其中,
// fn : 回调函数
// callback : this指向

map()

map() 方法返回一个新数组,数组中的元素为原始数组元素调用函数处理后的值。

map() 就相当于是在 forEach() 方法上的扩展,遍历所有元素并以相同的规则改变元素后返回一个新数组。用法如下

var arr = [1, 2, 3, 4]
var newArr = arr.map(function (val, index, arr) {
return val * index
})
console.log(arr) // [1, 2, 3, 4]
console.log(newArr) // [0, 2, 6, 12]

map() 就是加强版的 forEach() ,所以实现方法大致相同,只是多了一个返回新数组,实现如下

Array.prototype.myMap = function(fn, callback) {
var arr = [] // 与 forEach() 的区别所在,返回一个新数组
for(var i = 0, len = this.length; i < len; i ++) {
arr.push(fn.call(callback, this[i], i, this))
}
return arr
}

filter()

filter() 方法创建一个新的数组,新数组中的元素是通过检查指定数组中符合条件的所有元素。

filter 过滤,透过;顾名思义,filter() 就是过滤元素的方法,返回一个由过滤的元素组成的新数组。用法如下

var arr = [1, 2, 3, 4, 5, 6, 7, 8, 9]
var newArr = arr.filter(function(val, index, arr) {
return (val % 2 == 0)
})
console.log(arr) // [1, 2, 3, 4, 5, 6, 7, 8, 9]
console.log(newArr) // [2, 4, 6, 8]

filter() 方法实现的重点就在于理解 return 返回值的真假决定了正在遍历的该元素是否加入新数组。在实现过程中用到了push()方法,此方法会在后面详细介绍。

Array.prototype.myFilter = function (fn, callback) {
var arr = []
if (typeof fn === 'function') {
for(var k = 0, length3 = this.length; k < length3; k++){
// 回调函数的返回值必定是一个 boolean 类型的
// 所以可以直接利用 ‘&&’ 来判断当返回值为真的时候,将此元素加入到新数组之中
fn.call(callback, this[k], k, this) && arr.push(this[k])
}
}
return arr
}

some()

some() 方法用于检测数组中的元素是否满足指定条件(函数提供)。

some 一些。some() 就是当数组中只要有一个元素符合条件就返回真。用法如下

var arr = [1, 2, 3, 4, 5, 6, 7, 8, 9]
var flag = arr.some(function(val, index, arr) {
return val === 5
})
console.log(flag) // true

some() 看起来用法和上面的几种差不多,实现起来自然也不会有太大差别,仅仅是多了一层 if 判断,判断是否符合条件,实现如下

Array.prototype.mySome = function (fn, callback) {
// 这个不知道该称为什么点的一个点是用来设定当数组中有符合条件的值就是返回真
// 并同时跳出循环,因为 some 只需要有至少一个满足就可以了
var flag = false
if (typeof fn === 'function') {
for(var j = 0, length2 = this.length; j < length2; j++){
if (flag == true) { break }
// 这句是为了严谨起见
// 虽然本身就已经是一个 boolean 值了,但是还是要加 !! 来强转一下
flag = !! fn.call(callback, this[j], j, this)
}
}
return flag
}

every()

every() 方法用于检测数组所有元素是否都符合指定条件(通过函数提供)。

every 所以。说到some,自然离不开every。every就是判断数组中是否所有的元素都满足条件,一旦有一个不满足就返回 false,用法如下

var arr = [-1, 0, 1, 2, 3, 4, 5, 6, 7]
var flag = arr.every(function(val ,index, arr) {
return val > 0
})
console.log(flag) // false
 every() 与 some() 的实现方法相似,只有在真假的判断上有些许区别,不多说,直接看代码

Array.prototype.myEvery = function (fn, callback) {
// every 的设定就和 some 的完全相反了,some 是要至少一个,
// every 是要全部满足,所以一旦有一个不满足就跳出循环并返回 false
var flag = true
if (typeof fn === 'function') {
for(var j = 0, length2 = this.length; j < length2; j++){
if (flag === false) { break }
flag = !!fn.call(callback, this[j], j, this)
}
}
return flag
}

copyWithin()

copyWithin() 方法用于从数组的指定位置拷贝元素到数组的另一个指定位置中。

copyWithin 看名字组成,就是数组内部的拷贝,相当于选择其中的一部分元素复制,粘贴到另一部分去,这就是一个用法不同于上面所说的通式的方法了。值得注意的地方是,此方法会改变原数组的元素,但不改变其大小。他的参数列表及用法如下

// 参数列表
arr.copyWithin(target, start, end)
// target : 必选,复制到指定位置的索引值
// start : 可选,元素复制的起始索引,缺失则从 0 开始
// end : 可选,元素复制的结束索引,默认值是 arr.length,可取负值,表示倒数

// 用法
var arr = [1, 2, 3, 4, 5, 6, 7]
console.log(arr.copyWithin(3)) // (7)[1, 2, 3, 1, 2, 3, 4]
console.log(arr.copyWithin(3, 1)) // (7)[1, 2, 3, 2, 3, 1, 2]
console.log(arr.copyWithin(3, 2, 5)) // (7)[1, 2, 3, 3, 2, 3, 2]
console.log(arr.copyWithin(3, 4, 8)) // (7)[1, 2, 3, 2, 3, 2, 2]
console.log(arr.copyWithin(3, 4, -2)) // (7)[1, 2, 3, 3, 3, 2, 2]

第一次看到这个,肯定是看的头大吧, 不过没关系,接下来一步一步剖析整个过程

var arr = [1, 2, 3, 4, 5, 6, 7]
// 在这里我们定义 clArr 为要复制的数组片段

console.log(arr.copyWithin(3)) // (7)[1, 2, 3, 1, 2, 3, 4]

// start, end 均取默认值,即 0, 7
// 所以 clArr = arr = [1, 2, 3, 4, 5, 6, 7]
// target = 3, 即从 arr[3] 开始复制,同时还要保证 arr 的大小不发生改变
// 所以复制后数组为 [1, 2, 3, 1, 2, 3, 4]
// 当复制到数组最后一位时直接停止
// 从这里我们可以看出,重点就是找到clArr,只要找到clArr,一切都就迎刃而解

console.log(arr.copyWithin(3, 1)) // (7)[1, 2, 3, 2, 3, 1, 2]

// 因为copyWithin会改变原数组,所以此时的 arr = [1, 2, 3, 1, 2, 3, 4]
// start = 1, end = 7
// clArr = [2, 3, 1, 2, 3, 4]
// 可以看到 clArr.length = end - start

console.log(arr.copyWithin(3, 2, 5)) // (7)[1, 2, 3, 3, 2, 3, 2]

// arr = [1, 2, 3, 2, 3, 1, 2]
// start = 2, end = 5
// clArr = [3, 2, 3]

console.log(arr.copyWithin(3, 4, 8)) // (7)[1, 2, 3, 2, 3, 2, 2]

// arr = [1, 2, 3, 3, 2, 3, 2]
// start = 4, end = 8
// clArr = [2, 3, 2]
// 可以看到虽然理论上 clArr.length = 4,但是由于start + clArr.length
// 已经超出原数组的长度,所以 clArr.length 会自动以原数组长度为准

console.log(arr.copyWithin(3, 4, -2)) // (7)[1, 2, 3, 3, 3, 2, 2]

// arr = [1, 2, 3, 2, 3, 2, 2]
// 由定义我们知道负值就是倒数,所以我们首先将其转换成整数
// 即 end = arr.length + end = 5
// start = 4, end = 5
// clArr = [3]

看到这里相信你已经看懂了吧,会用了,下一步就是自己实现了,上面的过程基本也介绍了整个过程,就是首先确定 start 与 end ,然后根据二者的值确定要复制的数组片段,再根据 target 确定从何处开始复制。下面就是自己的实现

Array.prototype.myCopyWithin = function(target, start, end) {
var arr = this, len = this.length

// 若传入了值就用传入的值,未传入就是用默认值

var start = start || 0
var end = end || len

// 这里的实现与上面略有不同,这里首先判断 end 的值
// 如果大于数组长度就给end 赋值数组长度,小于0,则取正序值

end = end > len ? len : (end < 0 ? len + end : end)
var clArr = []

// index 是辅助索引值,len2 是 clArr 的长度

var index = start, len2 = end - start

// 遍历原数组,确定 clArr

for ( var i = 0; i < len2; i ++) {
clArr[i] = this[index]
index ++
}
index = 0

// len3 是遍历复制的结尾索引,加判断是防止 len3 大于原数组长度导致出错

var len3 = target + len2
len3 = len3 > len ? len : len3

// 遍历 clArr,将clArr复制到原数组指定位置

for ( var j = target; j < len3; j ++) {
arr[j] = clArr[index]
index ++
}

// 返回原数组

return arr
}

fill()

fill() 方法用一个固定值填充一个数组中从起始索引到终止索引内的全部元素。不包括终止索引。

fill 填充,一个和 copyWithin() 类似的方法,因为他们都是往数组里面填充一个片段,不同的是一个填充的是本来的数组的内容,一个填充的是一个固定值。其参数列表与用法如下

// 参数列表

arr.fill(val, start, end)
// val : 必选,用来填充数组元素的值
// start : 可选,起始索引,默认值为0
// end : 可选,结束索引,默认值为arr.length

// 用法

var arr = [1, 2, 3, 4, 5, 6, 7]
console.log(arr.fill(5, 0, 3)) // (7) [5, 5, 5, 4, 5, 6, 7]
console.log(arr.fill(3, 5)) // (7) [5, 5, 5, 4, 5, 3, 3]
console.log(arr.fill(6)) // (7) [6, 6, 6, 6, 6, 6, 6]

用法看起来也是一目了然,实现起来也是很容易,实现如下

Array.prototype.myFill = function() {
var arr = this
var val = arguments[0], start = arguments[1] || 0, end = arguments[2] || arr.length
for(var i = start; i < end; i ++) {
arr[i] = val
}
return arr
}

concat()

concat() 方法用于连接两个或多个数组。

concat 合并多个数组,就是将传入的多个数组按顺序连接在目标数组后面,并且返回新数组 ,参数列表及用法如下

// 参数列表
arr.concat(arr1, arr2, ...arrN)
// arr1, arr2, ... arrN : 必选,该参数可以是具体的值,也可以是数组对象。可以是任意多个。

// 用法
var arr1 = [1, 2, 3, 4]
var arr2 = [2, 3, 4, 5]
var arr3 = [3, 4, 5, 6]
var num1 = 7
var newArr1 = arr1.concat(arr2, arr3)
var newArr2 = arr1.concat(arr2)
var newArr3 = arr1.concat(num1)
console.log(newArr1) // [1, 2, 3, 4, 2, 3, 4, 5, 3, 4, 5, 6]
console.log(newArr2) // [1, 2, 3, 4, 2, 3, 4, 5]
console.log(newArr3) // [1, 2, 3, 4, 7]

此方法就是首先复制一个原数组的副本,然后将要连接的数组(数值)都拼接到后面,实现起来也很简单

Array.prototype.myConcat = function () {
var arr = this
// 得到形参列表的长度,判断需要几次循环来拼接
var len = arguments.length
for (let i = 0; i < len; i ++ ) {
// 如果要拼接的是数组
if (Object.prototype.toString.call(arguments[i]) === '[object Array]') {
var len2 = arguments[i].length
for(let j = 0; j < len2; j ++) {
arr[arr.length] = arguments[i][j]
}
} else { // 如果是数值
arr[arr.length] = arguments[i]
}
}
return arr
}
文章作者: JaCo Wu
文章链接: https://jacokwu.cn/blog/2018/09/13/原-JS原生数组方法的用法及其实现/
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 JaCo Wu的博客