Эта статья является превью следующей большой статьи "Вопрос души нативного JS (середина)". В следующей статье будут реализованы некоторые часто используемые методы в массиве со ссылкой на исходный код V8. Можно сказать, что вся сеть独家首发
, добро пожаловать, чтобы следовать.
Метод массива в движке V8 реализован на языке JS, в конце также прикреплена ссылка Если у вас есть какие-либо сомнения относительно моего кода, пожалуйста, сверьтесь с исходным кодом в любое время. И в конце прикрепил тестовый код, все про тесты пройденыMDN的所有测试用例
.
splice, возможно, является одним из самых популярных методов массива с гибким и простым в использовании API. Теперь разберемся с использованием:
-
- splice(position, count) означает удаление элементов count, начиная с позиции, индексированной по позиции
-
- splice(position, 0, ele1, ele2, ...) означает вставку ряда элементов после элемента, индексированного по позиции
-
- splice(postion, count, ele1, ele2, ...) означает, начиная с позиции, индексированной по позиции, удаляя элементы count, а затем вставляя серию элементов
-
- Возвращаемое значение
被删除元素
состоит из数组
.
- Возвращаемое значение
Далее реализуем этот метод.
Для начала разберемся с идеей реализации.
Первоначальная реализация
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;
}
Сначала скопируйте удаленный элемент следующим образом:
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;
}
Когда количество добавляемых элементов меньше количества удаляемых элементов, как показано на рисунке:
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];
}
}
};
Когда количество добавленных элементов больше, чем количество удаленных элементов, как показано на рисунке:
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];
}
}
}
};
Оптимизация 1: граничные случаи параметров
Когда пользователь отправляет недопустимый 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);
//...
}
Оптимизация 2: массив представляет собой запечатанный объект или замороженный объект.
Что такое закрытый объект?
Печать объекта не является расширяемым объектом, и есть члены атрибута [[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!')
}
Что ж, теперь я написал относительно полный сплайс, а именно:
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 660