这篇文章是下一波大文章《原生JS灵魂之问(中)》的预告,下波文章会参照 V8 源码将数组中一些经常使用的的方法实现一遍,能够说全网独家首发
,欢迎关注。git
V8引擎中的数组方法采用JS语言实现,连接也附在了最后,若是对个人代码有质疑,随时对照源码检查。而且测试代码已经附在最后,亲测所有经过了MDN的全部测试用例
。github
splice 能够说是最受欢迎的数组方法之一,api 灵活,使用方便。如今来梳理一下用法:api
被删除元素
组成的数组
。接下来咱们实现这个方法。数组
首先咱们梳理一下实现的思路。frontend
Array.prototype.splice = function(startIndex, deleteCount, ...addElements) {
let argumentsLen = arguments.length;
let array = Object(this);
let len = array.length;
let deleteArr = new Array(deleteCount);
// 拷贝删除的元素
sliceDeleteElements(array, startIndex, deleteCount, deleteArr);
// 移动删除元素后面的元素
movePostElements(array, startIndex, len, deleteCount, addElements);
// 插入新元素
for (let i = 0; i < addElements.length; i++) {
array[startIndex + i] = addElements[i];
}
array.length = len - deleteCount + addElements.length;
return deleteArr;
}
复制代码
先拷贝删除的元素,以下所示:post
const sliceDeleteElements = (array, startIndex, deleteCount, deleteArr) => {
for (let i = 0; i < deleteCount; i++) {
let index = startIndex + i;
if (index in array) {
let current = array[index];
deleteArr[i] = current;
}
}
};
复制代码
而后对删除元素后面的元素进行挪动, 挪动分为三种状况:测试
当二者相等时,优化
const movePostElements = (array, startIndex, len, deleteCount, addElements) => {
if (deleteCount === addElements.length) return;
}
复制代码
当添加的元素个数小于删除的元素时, 如图所示:ui
const movePostElements = (array, startIndex, len, deleteCount, addElements) => {
//...
// 若是添加的元素和删除的元素个数不相等,则移动后面的元素
if(deleteCount > addElements.length) {
// 删除的元素比新增的元素多,那么后面的元素总体向前挪动
// 一共须要挪动 len - startIndex - deleteCount 个元素
for (let i = startIndex + deleteCount; i < len; i++) {
let fromIndex = i;
// 将要挪动到的目标位置
let toIndex = i - (deleteCount - addElements.length);
if (fromIndex in array) {
array[toIndex] = array[fromIndex];
} else {
delete array[toIndex];
}
}
// 注意注意!这里咱们把后面的元素向前挪,至关于数组长度减少了,须要删除冗余元素
// 目前长度为 len + addElements - deleteCount
for (let i = len - 1; i >= len + addElements.length - deleteCount; i --) {
delete array[i];
}
}
};
复制代码
当添加的元素个数大于删除的元素时, 如图所示:this
const movePostElements = (array, startIndex, len, deleteCount, addElements) => {
//...
if(deleteCount < addElements.length) {
// 删除的元素比新增的元素少,那么后面的元素总体向后挪动
// 思考一下: 这里为何要从后往前遍历?从前日后会产生什么问题?
for (let i = len - 1; i >= startIndex + deleteCount; i--) {
let fromIndex = i;
// 将要挪动到的目标位置
let toIndex = i + (addElements.length - deleteCount);
if (fromIndex in array) {
array[toIndex] = array[fromIndex];
} else {
delete array[toIndex];
}
}
}
};
复制代码
当用户传来非法的 startIndex 和 deleteCount 或者负索引的时候,须要咱们作出特殊的处理。
const computeStartIndex = (startIndex, len) => {
// 处理索引负数的状况
if (startIndex < 0) {
return startIndex + len > 0 ? startIndex + len: 0;
}
return startIndex >= len ? len: startIndex;
}
const computeDeleteCount = (startIndex, len, deleteCount, argumentsLen) => {
// 删除数目没有传,默认删除startIndex及后面全部的
if (argumentsLen === 1)
return len - startIndex;
// 删除数目太小
if (deleteCount < 0)
return 0;
// 删除数目过大
if (deleteCount > len - deleteCount)
return len - startIndex;
return deleteCount;
}
Array.prototype.splice = function (startIndex, deleteCount, ...addElements) {
//,...
let deleteArr = new Array(deleteCount);
// 下面参数的清洗工做
startIndex = computeStartIndex(startIndex, len);
deleteCount = computeDeleteCount(startIndex, len, deleteCount, argumentsLen);
// 拷贝删除的元素
sliceDeleteElements(array, startIndex, deleteCount, deleteArr);
//...
}
复制代码
什么是密封对象?
密封对象是不可扩展的对象,并且已有成员的[[Configurable]]属性被设置为false,这意味着不能添加、删除方法和属性。可是属性值是能够修改的。
什么是冻结对象?
冻结对象是最严格的防篡改级别,除了包含密封对象的限制外,还不能修改属性值。
接下来,咱们来把这两种状况一一排除。
// 判断 sealed 对象和 frozen 对象, 即 密封对象 和 冻结对象
if (Object.isSealed(array) && deleteCount !== addElements.length) {
throw new TypeError('the object is a sealed object!')
} else if(Object.isFrozen(array) && (deleteCount > 0 || addElements.length > 0)) {
throw new TypeError('the object is a frozen object!')
}
复制代码
好了,如今就写了一个比较完整的splice,以下:
const sliceDeleteElements = (array, startIndex, deleteCount, deleteArr) => {
for (let i = 0; i < deleteCount; i++) {
let index = startIndex + i;
if (index in array) {
let current = array[index];
deleteArr[i] = current;
}
}
};
const movePostElements = (array, startIndex, len, deleteCount, addElements) => {
// 若是添加的元素和删除的元素个数相等,至关于元素的替换,数组长度不变,被删除元素后面的元素不须要挪动
if (deleteCount === addElements.length) return;
// 若是添加的元素和删除的元素个数不相等,则移动后面的元素
else if(deleteCount > addElements.length) {
// 删除的元素比新增的元素多,那么后面的元素总体向前挪动
// 一共须要挪动 len - startIndex - deleteCount 个元素
for (let i = startIndex + deleteCount; i < len; i++) {
let fromIndex = i;
// 将要挪动到的目标位置
let toIndex = i - (deleteCount - addElements.length);
if (fromIndex in array) {
array[toIndex] = array[fromIndex];
} else {
delete array[toIndex];
}
}
// 注意注意!这里咱们把后面的元素向前挪,至关于数组长度减少了,须要删除冗余元素
// 目前长度为 len + addElements - deleteCount
for (let i = len - 1; i >= len + addElements.length - deleteCount; i --) {
delete array[i];
}
} else if(deleteCount < addElements.length) {
// 删除的元素比新增的元素少,那么后面的元素总体向后挪动
// 思考一下: 这里为何要从后往前遍历?从前日后会产生什么问题?
for (let i = len - 1; i >= startIndex + deleteCount; i--) {
let fromIndex = i;
// 将要挪动到的目标位置
let toIndex = i + (addElements.length - deleteCount);
if (fromIndex in array) {
array[toIndex] = array[fromIndex];
} else {
delete array[toIndex];
}
}
}
};
const computeStartIndex = (startIndex, len) => {
// 处理索引负数的状况
if (startIndex < 0) {
return startIndex + len > 0 ? startIndex + len: 0;
}
return startIndex >= len ? len: startIndex;
}
const computeDeleteCount = (startIndex, len, deleteCount, argumentsLen) => {
// 删除数目没有传,默认删除startIndex及后面全部的
if (argumentsLen === 1)
return len - startIndex;
// 删除数目太小
if (deleteCount < 0)
return 0;
// 删除数目过大
if (deleteCount > len - deleteCount)
return len - startIndex;
return deleteCount;
}
Array.prototype.splice = function(startIndex, deleteCount, ...addElements) {
let argumentsLen = arguments.length;
let array = Object(this);
let len = array.length;
let deleteArr = new Array(deleteCount);
startIndex = computeStartIndex(startIndex, len);
deleteCount = computeDeleteCount(startIndex, len, deleteCount, argumentsLen);
// 判断 sealed 对象和 frozen 对象, 即 密封对象 和 冻结对象
if (Object.isSealed(array) && deleteCount !== addElements.length) {
throw new TypeError('the object is a sealed object!')
} else if(Object.isFrozen(array) && (deleteCount > 0 || addElements.length > 0)) {
throw new TypeError('the object is a frozen object!')
}
// 拷贝删除的元素
sliceDeleteElements(array, startIndex, deleteCount, deleteArr);
// 移动删除元素后面的元素
movePostElements(array, startIndex, len, deleteCount, addElements);
// 插入新元素
for (let i = 0; i < addElements.length; i++) {
array[startIndex + i] = addElements[i];
}
array.length = len - deleteCount + addElements.length;
return deleteArr;
}
复制代码
以上代码对照MDN文档中的全部测试用例亲测经过。
相关测试代码请前往: 传送门
最后给你们奉上V8源码,供你们检查: V8数组 splice 源码第 660 行