用最简单的 for 循环来实现数组的方法
JavaScript 数组中有很多实用的方法,特别是跟遍历数组有关联的方法。比如:forEach,map,filter,some,every,find,reduce 等等,它们都可以通过简洁的代码来完成特定的功能。在使用过程中,大家肯定有好奇它们实现原理的时候,那么本文就是来解析这些方法的原理,让你对它们的理解更上一层楼。
forEach
forEach 方法其实就是函数版的 for 循环,在同步执行的情况下,它跟普通 for 循环实现的功能没有区别。而在异步的情况下,forEach 不会等待 Promise 的状态,谨慎使用。forEach 有两个参数:
-
回调函数 —
cb,数组的每个元素都会执行一次这个函数,回调函数中有三个参数:value:当前的元素值。index:当前的元素索引。array:调用forEach方法的数组本身。
-
回调函数的
this指向 —thisArg,可选。在浏览器环境中,回调函数默认的this指向是window,在 Node 环境中,默认的this指向是undefined。注意,如果回调函数是箭头函数,那么这个参数将不会起作用。
接下来所有方法中如果没有做特殊说明,那么它们的参数以及回调函数中的参数都和
forEach方法一样。
用法
const arr = [1, 2, 3]
arr.forEach((ele, index, array) => {
console.log(ele, index, array);
})
// 1 0 [1, 2, 3]
// 2 1 [1, 2, 3]
// 3 2 [1, 2, 3]
forEach 方法不会遍历空槽元素,所谓空槽元素,就是没有明确地指定数组的元素值。比如:
// arr[1] 就是空槽元素
const arr = [1, , 2];
空槽元素严格等于 undefined ,但如果显式指定了元素的值为 undefined,forEach 方法依然会遍历它。比如:
const arr = [1, , 3]
console.log(arr[1] === undefined); // true
arr.forEach((ele, index, array) => {
console.log(ele, index, array);
})
// 1 0 [1, 空, 3]
// 3 2 [1, 空, 3]
arr[1] = undefined;
arr.forEach((ele, index, array) => {
console.log(ele, index, array);
})
// 1 0 [1, undefined, 3]
// undefined 1 [1, undefined, 3]
// 3 2 [1, undefined, 3]
接下来所有方法中如果没有做特殊说明,那么其空槽元素的处理和
forEach方法一样。
实现原理
所以在 forEach 内部原理中,空槽元素不能通过 undefined 进行判断,不准确。而需要使用 in 操作符来判断。
Array.prototype._forEach = function (cb, thisArg) {
// 若第一个参数不是函数,抛出错误
if (typeof cb !== "function") {
throw Error(`${cb} is not a function`);
}
// 指定默认的 this 指向,浏览器环境中设置为 window,Node 环境中设置为 undefined
if (typeof window !== "undefined") {
thisArg = thisArg || window;
}
// 这里的 this 指向数组本身
for (let i = 0; i < this.length; i++) {
// 空槽元素跳过
if (i in this) {
cb.call(thisArg, this[i], i, this);
}
}
};
接下来所有方法的实现原理都会挂载到数组构造函数的原型上,并且方法名称是 _ 加上原方法名。
map
和 forEach 不同的是, map 方法会返回一个新的数组,这个新数组的每个元素由 map 方法中的回调函数的返回值组成。
用法
const arr = [1, 2, 3];
const mapArr = arr.map(ele => ele * 2);
console.log(mapArr); // 2, 4, 6
实现原理
Array.prototype._map = function (cb, thisArg) {
if (typeof cb !== "function") {
throw Error(`${cb} is not a function`);
}
if (typeof window !== "undefined") {
thisArg = thisArg || window;
}
// 新数组
const mapArr = [];
for (let i = 0; i < this.length; i++) {
if (i in this) {
// 新数组的元素值是 cb 函数的返回值
mapArr[i] = cb.call(thisArg, this[i], i, this);
}
}
return mapArr;
};
filter
filter 用于筛选原数组中满足条件的元素,其中的条件就是回调函数的返回值(true 或 false)。
用法
const arr = [
{ name: "jack", age: 18 },
{ name: "jack's brother", age: 12 },
{ name: "jack's sister", age: 8 },
];
const filterArr = arr.filter((ele) => ele.age >= 18);
console.log(filterArr); // [{ name: "jack", age: 18 }]
这里需要注意的是,filter 函数返回的新数组是对原数组的浅拷贝。
实现原理
Array.prototype._filter = function (cb, thisArg) {
if (typeof cb !== "function") {
throw Error(`${cb} is not a function`);
}
if (typeof window !== "undefined") {
thisArg = thisArg || window;
}
// 新数组
const filterArr = [];
for (let i = 0; i < this.length; i++) {
if (i in this) {
// 若回调函数的值是 true,说明元素符合条件,加入新数组
cb.call(thisArg, this[i], i, this) && filterArr.push(this[i]);
}
}
return filterArr;
};
some
some 方法用于检测数组中是否至少有一个元素符合条件,其中的条件是回调函数的返回值(true 或 false)。如果有一个元素符合条件,那么 some 方法就返回 true,否则返回 false。
用法
const arr = [2, 4, 3, 1];
let res = arr.some(ele => ele > 3);
console.log(res); // true
res = arr.some(ele => ele > 5);
console.log(res); // false,没有一个元素大于5
实现原理
Array.prototype._some = function (cb, thisArg) {
if (typeof cb !== "function") {
throw Error(`${cb} is not a function`);
}
if (typeof window !== "undefined") {
thisArg = thisArg || window;
}
for (let i = 0; i < this.length; i++) {
if (i in this) {
// 元素符合条件,some 方法返回 true
if (cb.call(thisArg, this[i], i, this)) {
return true;
}
}
}
return false;
};
every
every 方法用于检测数组中是否所有元素都符合条件,其中的条件是回调函数的返回值(true 或 false)。如果有一个元素不符合条件,那么 every 方法就返回 false,否则,所有元素都符合条件,返回 true。与 some 方法的判断逻辑正好相反。
用法
const arr = [2, 4, 3, 1];
let res = arr.every(ele => ele > 3);
console.log(res); // false
res = arr.every(ele => ele > 0);
console.log(res); // true,所有元素都大于 0
实现原理
Array.prototype._every = function (cb, thisArg) {
if (typeof cb !== "function") {
throw Error(`${cb} is not a function`);
}
if (typeof window !== "undefined") {
thisArg = thisArg || window;
}
for (let i = 0; i < this.length; i++) {
if (i in this) {
// 有一个元素不符合条件,返回 false
if (!cb.call(thisArg, this[i], i, this)) {
return false;
}
}
}
// 所有元素都符合条件,最终返回 true
return true;
};
find
find 方法用于找出数组中第一个满足条件的元素,其中的条件是回调函数的返回值(true 或 false)。如果所有元素都不满足条件,那么 find 方法返回 undefined。
注意,find 方法会遍历空槽元素。
用法
const arr = [, 2, 3];
const element = arr.find((ele, index) => {
console.log(ele, index);
return ele > 1;
});
// undefined 0
// 2 1
// 3 2
console.log(element); // 2
实现原理
Array.prototype._find = function (cb, thisArg) {
if (typeof cb !== "function") {
throw Error(`${cb} is not a function`);
}
if (typeof window !== "undefined") {
thisArg = thisArg || window;
}
for (let i = 0; i < this.length; i++) {
// 由于 find 方法会遍历空槽元素,所以移除 i in this 的判断
// 找到满足条件的第一个元素,并返回它
if (cb.call(thisArg, this[i], i, this)) {
return this[i];
}
}
// 所有元素都不符合条件,最终返回 undefined
return undefined;
};
同理,findLast 方法和 find 方法的实现原理差不多,只不过 findLast 方法是从最后一个元素开始遍历到第一个元素。
findIndex
findIndex 方法与 find 方法类似,只不过它用于找出数组中第一个满足条件的元素的索引,如果所有元素都不满足条件,那么 findIndex 方法返回 -1。
findIndex 方法也会遍历空槽元素。
用法
const arr = [, 2, 3];
const index = arr.findIndex((ele, index) => {
console.log(ele, index);
return ele > 1;
});
// undefined 0
// 2 1
// 3 2
console.log(index); // 1
实现原理
Array.prototype._findIndex = function (cb, thisArg) {
if (typeof cb !== "function") {
throw Error(`${cb} is not a function`);
}
if (typeof window !== "undefined") {
thisArg = thisArg || window;
}
for (let i = 0; i < this.length; i++) {
// 找到满足条件的第一个元素,并返回它的索引
if (cb.call(thisArg, this[i], i, this)) {
return i;
}
}
// 所有元素都不符合条件,最终返回 -1
return -1;
};
同理,findLastIndex 方法和 findIndex 方法的实现原理差不多,只不过 findLastIndex 方法是从最后一个元素开始遍历到第一个元素。
indexOf
indexOf 用于找出数组中第一次出现指定元素的索引,如果不存在则返回 -1。跟 findIndex 方法的作用很像,但 indexOf 的参数不一样,它的参数有两个:
-
要查找的元素 —
searchElement。indexOf的条件就是这个参数,返回这个参数的索引值。 -
开始查找的索引位置 —
fromIndex,可选,默认为 0。- 如果
fromIndex < 0,那么会进行隐式转换,fromIndex的实际值为fromIndex + array.length,如果实际值还是小于 0,那么fromIndex的值就为 0。 - 如果
fromIndex >= array.length,indexOf不会进行遍历,直接返回 -1。 - 如果
fromIndex是字符串或小数,那么会进行向下取整转换;如果转换的结果是NaN,那fromIndex的值为 0。
- 如果
用法
const arr = ["a", "b", "c", "d", "e"];
console.log(arr.indexOf("c")); // 2
console.log(arr.indexOf("c", "2.9")); // 2
console.log(arr.indexOf("c", "3.1")); // -1
console.log(arr.indexOf("c", -3)); // 2
console.log(arr.indexOf("c", -2)); // -1
实现原理
Array.prototype._indexOf = function (searchElement, fromIndex) {
fromIndex = fromIndex ?? 0; // 设置 fromIndex 的默认值
fromIndex = Math.floor(fromIndex); // 向下取整 fromIndex
if (fromIndex !== fromIndex) {
// 如果是 NaN,则赋值为 0
fromIndex = 0;
}
if (fromIndex < 0) {
fromIndex = fromIndex + this.length;
//经过转化后,fromIndex 小于 0 时的实际值
fromIndex = fromIndex < 0 ? 0 : fromIndex;
}
for (let i = fromIndex; i < this.length; i++) {
if (i in this && this[i] === searchElement) {
return i;
}
}
// 遍历所有元素没有找到 searchElement,返回 -1
return -1;
};
同理,lastIndexOf 方法的实现原理也类似,只不过遍历的方向是相反的,fromIndex 表示反向遍历的起始位置。
从实现原理中,我们可以得出,indexOf 方法或者 lastIndexOf 方法是无法找出数组中的 NaN 元素,因为 NaN 跟自身不相等。
includes
includes 方法用于判断数组中是否包含一个指定的值,如果包含,则返回 true,否则返回 false。
includes 方法的参数和 indexOf 方法的参数是一样的,在这里就不过多赘述了。跟 indexOf 不同的是,includes 方法会遍历空槽元素,而且还能判断 NaN 元素是否在数组中。
用法
console.log([1, 2, 3]._includes(2)); // true
console.log([1, 2, 3]._includes(4)); // false
console.log([1, 2, 3]._includes(3, 3)); // false
console.log([1, 2, 3]._includes(3, -1)); // true
console.log([1, 2, NaN]._includes(NaN)); // true
console.log([1, , 3]._includes(undefined)); // true
实现原理
Array.prototype._includes = function (searchElement, fromIndex) {
fromIndex = fromIndex ?? 0; // 设置 fromIndex 的默认值
fromIndex = Math.floor(fromIndex); // 向下取整 fromIndex
if (fromIndex !== fromIndex) {
// 如果是 NaN,则赋值为 0
fromIndex = 0;
}
if (fromIndex < 0) {
fromIndex = fromIndex + this.length;
//经过转化后,fromIndex 小于 0 时的实际值
fromIndex = fromIndex < 0 ? 0 : fromIndex;
}
const isNaN = searchElement !== searchElement;
for (let i = fromIndex; i < this.length; i++) {
if (searchElement === this[i]) {
return true;
} else if (isNaN && this[i] !== this[i]) {
// 如果 searchElement 是 NaN,并且数组中的元素也是 NaN,返回true
return true;
}
}
return false;
};
reduce
reduce 方法用于数组的合并计算,它会将上一次回调函数的执行结果,传给当前回调函数,最终返回一个结果值,比如数组的求和,找出数组的最大值等等。reduce 有两个参数:
-
第一个参数是回调函数 —
cb,数组的每个元素都会执行一次这个函数,回调函数中有四个参数:accumulator:上一个回调函数的结果。在第一次调用时,它的值为reduce方法的第二个参数值,如果没有指定第二个参数值,那么它的值为第一个非空槽元素值。currentValue:当前的元素值。在第一次调用时,如果指定了reduce方法的第二个参数值,则为第一个元素值,否则为第二个元素值。currentIndex:当前的元素值对应的索引。array: 调用reduce方法的数组本身。
-
第二个参数是
accumulator的初始值,可选。如果不指定,那么accumulator的初始值就是第一个非空槽元素值。
用法
// 求和
// 指定 accumulator 的初始值的情况下
const arr = [, 2, 3, 4];
let sum = arr.reduce((accumulator, curValue, curIndex, array) => {
console.log(accumulator, curValue, curIndex);
return accumulator + curValue;
}, 0);
// 0 2 1
// 2 3 2
// 5 4 3
console.log(sum); // 9
// 不指定 accumulator 的初始值的情况下
const arr = [, 2, 3, 4];
let sum = arr.reduce((accumulator, curValue, curIndex, array) => {
console.log(accumulator, curValue, curIndex);
return accumulator + curValue;
});
// 2 3 2
// 5 4 3
console.log(sum); // 9
实现原理
Array.prototype._reduce = function (cb, initialValue) {
if (typeof cb !== "function") {
throw Error(`${cb} is not a function`);
}
let start = 0;
let accumulator = initialValue;
// 如果没有指定 accumulator 的初始值,那么就用第一个非空槽元素作为初始值。
if (initialValue === undefined) {
for (; start < this.length; start++) {
if (start in this) {
accumulator = this[start++];
break;
}
}
}
for (; start < this.length; start++) {
if (start in this) {
// 保存上一次回调函数的计算结果,并将它传给当前回调函数
accumulator = cb(accumulator, this[start], start, this);
}
}
// 最终的合并计算结果
return accumulator;
};
同理,reduceRight 方法和 reduce 方法的实现原理差不多,只不过 reduceRight 方法是从最后一个元素开始遍历到第一个元素。 如果没有指定第二个参数,那么 accumulator 的初始值是最后一个非空槽元素。
slice
slice 方法用于提取包含在指定索引范围(start <= index < end)的所有元素,这些元素存储在一个新的数组上。slice 方法有两个参数:
-
start:开始索引,可选,默认为 0。- 如果
start < 0,那么会进行隐式转换,start的实际值为start + array.length,如果实际值还是小于 0,那么start的值就为 0。 - 如果
start >= array.length,则不会提取任何元素,slice方法返回空数组。 - 如果
start是字符串或小数,那么会进行向下取整转换;如果转换的结果是NaN,那start的值为 0。
- 如果
-
end:结束索引,可选,默认为数组的长度(array.length)。所以slice方法会提取到不包括end索引的元素。- 如果
end < 0,那么会进行隐式转换,end的实际值为end + array.length,如果实际值还是小于 0,那么end的值就为 0,则不会提取任何元素。 - 如果
end > array.length,end值为array.length。 - 如果
start >= end,则不会提取任何元素。 - 如果
end是字符串或小数,那么会进行向下取整转换;如果转换的结果是NaN,那end的值为 0。
- 如果
总得来说,start 参数不能太大,不能大于或等于数组的长度,否则不会提取任何元素;end 参数不能太小,不能小于或等于 start,不能等于 0,负数的情况下,不能小于或等于 -array.length,否则也不会提取任何元素。
用法
const arr = [1, 2 ,3, 4];
console.log(arr.slice()); // [1, 2 ,3, 4]
console.log(arr.slice(-10)); // [1, 2 ,3, 4]
console.log(arr.slice(1)); // [2 ,3, 4]
console.log(arr.slice(1, 3)); // [2 ,3]
console.log(arr.slice(-3, -1)); // [2 ,3]
console.log(arr.slice(-3)); // [2 ,3, 4]
console.log(arr.slice(4)); // []
console.log(arr.slice(3, 2)); // []
console.log(arr.slice(-2, -3)); // []
需要注意的是,slice 返回的新数组是对原数组的浅拷贝,并且空槽元素也会被保留到新数组中。
实现原理
// 将变量进行向下取整
function convertInt(data) {
data = Math.floor(data);
if (data !== data) {
// 如果转换成 NaN,就赋值为 0
data = 0;
}
return data;
}
// 变量小于 0 时的处理逻辑
function smallerThanZero(data) {
data = data + this.length;
// 经过转化后,变量还是小于 0 的实际值
data = data < 0 ? 0 : data;
}
Array.prototype._slice = function (start, end) {
start = start ?? 0; // 设置 start 的默认值
end = end ?? this.length; // 设置 end 的默认值
// start,end 向下取整
start = convertInt(start);
end = convertInt(end);
if (start < 0) {
start = smallerThanZero(start);
}
if (end < 0) {
end = smallerThanZero(end);
} else if (end > this.length) {
end = this.length;
}
const res = [];
for (let i = start, resIndex = 0; i < end; i++, resIndex++) {
if (i in this) {
// 通过索引的方式赋值,可以保留空槽元素
res[resIndex] = this[i];
}
}
return res;
};
concat
concat 方法用于合并多个数组,最终返回一个新数组。concat 方法的参数可以有 0 ~ n 个,
这些参数可以是一个数组或者某个类型值,如果不传任何参数,concat 返回的是对原数组的前浅拷贝。
用法
const arr = [1, 2, 3];
const arr1 = [4, 5, 6];
const arr2 = [, 5, 6];
const arr = [1, 2, 3];
const arr1 = [4, 5, 6];
const arr2 = [, 5, 6];
console.log(arr.concat(arr1)); // [1, 2, 3, 4, 5, 6]
console.log(arr.concat(arr2)); // [1, 2, 3, 空, 5, 6]
console.log(arr.concat(arr1, 7)); // [1, 2, 3, 4, 5, 6, 7]
concat 方法也会保留空槽元素。
实现原理
Array.prototype._concat = function () {
const res = [];
let resIndex = 0;
// concat 默认对原数组进行浅拷贝
for (let i = 0; i < this.length; i++, resIndex++) {
if (i in this) {
res[resIndex] = this[i];
}
}
// 合并多个数组或者其他值
for (let i = 0; i < arguments.length; i++) {
const param = arguments[i];
if (!(param instanceof Array)) {
res[resIndex++] = param;
} else {
for (let j = 0; j < param.length; j++, resIndex++) {
// 保留其他数组的空槽元素
if (j in param) {
res[resIndex] = param[j];
}
}
}
}
return res;
};
join
join 方法用英文逗号(,)或者指定的分隔符将数组的所有元素连接成一个字符串并返回这个字符串。如果数组只有一个元素,那么将直接返回这个元素。
join 方法的参数只有一个,那就是分隔符 — separator,可选,默认值是英文逗号(,)。
如果一个元素是 undefined 或 null,它将被转换为空字符串,而不是字符串 "undefined" 或 "null"。join 方法会遍历空槽元素,它也会被转换为空字符串。
用法
console.log([1, 2, 3].join()); // '1,2,3'
console.log([1, 2, 3].join(" + ")); // '1 + 2 + 3'
console.log([1, 2, 3].join("")); // '123'
console.log([1, , 3].join()); // '1,,3'
console.log([1, undefined, 3].join()); // '1,,3'
实现原理
Array.prototype._join = function (separator) {
separator = separator ?? ","; // 设置 separator 的默认值
let str = "";
for (let i = 0; i < this.length; i++) {
// null,undefined,空槽不会加入到字符串结果中,相当于空字符串的效果。
if (this[i] !== null && this[i] !== undefined) {
str += String(this[i]);
}
// 最后一个元素后面不加分隔符
if (i < this.length - 1) {
str += separator;
}
}
return str;
};
reverse
reverse 方法用于反转数组中的元素,它会就地修改原数组的内容,并返回原数组。reverse 方法没有任何参数。
用法
const arr = [1, 2, 3, 4];
console.log(arr.reverse()); // [4, 3, 2, 1]
实现原理
反转数组就表示数组中倒数第一个元素变成第一个元素,第一个元素变成倒数第一个元素,倒数第二个元素变成第二个元素,第二个元素变成倒数第二个元素....,以此类推。
所以,只需要把倒数第一个元素和第一个元素进行交换,倒数第二个元素和第二个元素进行交换...,直到全部交换完毕,就完成了数组的反转。
Array.prototype._reverse = function () {
let start = 0, end = this.length - 1;
while (start < end) {
[this[start], this[end]] = [this[end], this[start]];
start++;
end--;
}
return this;
};
splice
splice 用于删除、增加、替换元素到原数组上,它会就地修改原数组的内容。它的参数有:
-
start:表示要开始改变数组的索引位置。- 如果省略了
start,即splice方法不传任何参数,则不会对原数组做任何事情;但如果start是undefined或null,则start值为 0。 - 如果
start < 0,那么会进行隐式转换,start的实际值为start + array.length,如果实际值还是小于 0,那么start的值就为 0。 - 如果
start >= array.length,则start的值为array.length,不会删除任何元素,可以添加指定的元素。 - 如果
start是字符串或小数,那么会进行向下取整转换;如果转换的结果是NaN,那start的值为 0。
- 如果省略了
-
deleteCount:表示数组中要从start开始删除的元素数量,可选,默认值为从start到数组末尾的元素数量,即:array.length - start- 如果
deleteCount大于array.length - start,则deleteCount的值为array.length - start。 - 如果
deleteCount是undefined、null或小于 0,则deleteCount的值为 0。
- 如果
-
addItem1...addItemN:表示从start开始要添加到数组中的元素,可选。如果不指定添加元素,那么splice方法就是从数组中删除元素。
splice 方法的返回值是被删除的元素。
用法
const arr1 = arr.slice();
arr1.splice(2); // arr1: [1, 2]
const arr2 = arr.slice();
arr2.splice(2, 2); // arr2: [1, 2, 5, 6]
const arr3 = arr.slice();
arr3.splice(2, 0, 2.1, 2.2); // arr3: [1, 2, 2.1, 2.2, 3, 4, 5, 6]
const arr4 = arr.slice();
arr4.splice(2, 1, 2.1); // arr4: [1, 2, 2.1, 4, 5, 6]
实现原理
splice 方法是如何同时做到增加、删除的功能呢?首先,我们单独拿增加和删除的功能来分析一下:
假设有这样的一个数组:
const arr = [1, 2, 3, 4, 5];
- 如果我想从第 2 个位置起添加两个元素 —
2.1和2.2,期望的结果是[1, 2, 2.1, 2.2, 3, 4, 5]。该怎么做呢?首先,把3后面的元素(包括3)向后移动两位,这样2和3之间多出两个空槽元素;接着,把这两个空槽元素替换成2.1和2.2。 - 如果我想从第 2 个位置起删除两个个元素,期望的结果是
[1, 2, 5]。又该怎么做呢?首先,把5后面的元素(包括5)向前移动两位,把3和4通过后面的元素覆盖掉,达到删除的效果;接着,把数组的长度减 2。
现在很清楚了,删除元素就是后面的元素向前移动 n 位;增加元素就是后面的元素向后移动 n 位。现在的新问题是:如何在兼顾这两种功能的情况下,确定从哪个位置的元素开始移动,以及移动多少距离?
前面说到了,splice 方法的参数有 start 和 deleteCount,所以
- 对于单纯的增加元素来说,
deleteCount为 0,从start位置的元素开始向后移动 - 对于单纯的删除元素来说,
deleteCount大于 0,从start + deleteCount位置的元素开始向前移动。
因此,在兼顾增加和删除功能的情况下,是从 start + deleteCount 位置的元素开始移动的。
splice 方法还有添加元素的参数 addItem1 ... addItemN,我们把这些参数放到一个数组里 — addItems。因此,我们可以得知:
addItems.length就是增加元素时,向后移动的距离deleteCount就是删除元素时,向前移动的距离
最终,结合这两个参数,addItems.length - deleteCount,正数表示数组最后向后移动的距离,负数代表数组最后向前移动的距离。
代码实现:
// 将变量进行向下取整
function convertInt(data) {
data = Math.floor(data);
if (data !== data) {
// 如果转换成 NaN,就赋值为 0
data = 0;
}
return data;
}
Array.prototype._splice = function (start, deleteCount, ...addItems) {
if (arguments.length === 0) {
return;
}
start = start ?? 0;
start = convertInt(start);
if (start < 0) {
start = start + this.length;
start = start < 0 ? 0 : start;
} else if (start > this.length) {
start = this.length;
}
if (!(1 in arguments)) {
// 省略 deleteCount 时的默认值
deleteCount = this.length - start;
} else if (deleteCount === undefined || deleteCount === null) {
deleteCount = 0;
}
deleteCount = convertInt(deleteCount);
if (deleteCount > this.length - start) {
deleteCount = this.length - start;
} else if (deleteCount < 0) {
deleteCount = 0;
}
// 储存被删除的元素
const deleteArr = [];
// 从 start + deleteCount 索引位置的元素开始移动
const translateIndex = start + deleteCount;
// 每个元素要移动的距离,负数代表向前移动,正数代表向后移动
const translateDis = addItems.length - deleteCount;
const len = this.length;
for (let i = start; i < translateIndex; i++) {
deleteArr.push(this[i]);
}
if (translateDis < 0) {
// 删除,向前移动
for (let i = translateIndex; i < len; i++) {
this[i + translateDis] = this[i];
}
} else if (translateDis > 0) {
// 添加,向后移动
for (let i = len; i >= translateIndex; i--) {
this[i + translateDis] = this[i];
}
}
// 替换旧元素
for (let j = 0; j < addItems.length; j++) {
this[start++] = addItems[j];
}
this.length = len + translateDis;
return deleteArr;
};
总结
forEach、map、filter、some、every、find(findLast)、findIndex(findLastIndex)的参数都是一样的,其中map返回的是新数组,filter返回是对原数组进行浅拷贝的新数组。- 对于查找元素的方法,
includes可以判断数组中是否存在NaN,而find(findLast)、findIndex(findLastIndex)、indexOf不能。 slice、concat、filter都可以完整地浅拷贝原数组。find(findLast)、findIndex(findLastIndex)、includes都会遍历空槽元素,而slice、concat、join会保留空槽元素。- 在
splice方法中,增加元素就是向后移动,删除元素就是向前移动。start + deleteCount确定从哪个位置的元素开始移动;addItems.length - deleteCount确定移动的方向以及距离。
数组中还有一些有趣的方法,比如 flat、sort,但它们的原理不是通过简单的 for 循环来遍历数组中的元素就能实现的,关于它们的实现原理,后续会写相关的文章跟大家分享,敬请期待~
转载自:https://juejin.cn/post/7256066096947511357