用最简单的 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