這篇文章是下一波大文章《原生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 行