This article is the next big wave of articles “native JS soul ask (in)” notice, the next wave of articles will refer to V8 source array in some commonly used methods to achieve again, can be said to be exclusive network first, welcome attention.

V8 array method in the IMPLEMENTATION of JS language, link is also attached at the end, if you have doubts about my code, check the source code at any time. In addition, the test code has been attached at the end, and all the test cases of MDN have been passed.

Splice is arguably one of the most popular array methods, with a flexible API and easy to use. Now to sort out the usage:

    1. Splice (position, count) deletes count elements from the position index
    1. splice(position, 0, ele1, ele2, …) Represents the insertion of a series of elements from after the element in the Position index
    1. splice(postion, count, ele1, ele2, …) Deletes count elements from position index, and then inserts a sequence of elements
    1. The return value isDeleted elementConsisting of aAn array of.

Next we implement this method.

First, let’s go through the implementation ideas.

The preliminary implementation

Array.prototype.splice = function(startIndex, deleteCount, ... addElements)  {
  let argumentsLen = arguments.length;
  let array = Object(this);
  let len = array.length;
  let deleteArr = new Array(deleteCount);
   
  // Copy the deleted element
  sliceDeleteElements(array, startIndex, deleteCount, deleteArr);
  // Move the element after the deleted element
  movePostElements(array, startIndex, len, deleteCount, addElements);
  // Insert a new element
  for (let i = 0; i < addElements.length; i++) {
    array[startIndex + i] = addElements[i];
  }
  array.length = len - deleteCount + addElements.length;
  return deleteArr;
}
Copy the code

First copy the deleted element as follows:

const sliceDeleteElements = (array, startIndex, deleteCount, deleteArr) = > {
  for (let i = 0; i < deleteCount; i++) {
    let index = startIndex + i;
    if (index in array) {
      letcurrent = array[index]; deleteArr[i] = current; }}};Copy the code

Then move the element behind the deleted element, which can be divided into three cases:

  1. The number of elements added is equal to the number of elements removed
  2. The number of elements added is smaller than the number of elements removed
  3. The number of added elements is greater than the number of deleted elements

When they are equal to each other,

const movePostElements = (array, startIndex, len, deleteCount, addElements) = > {
  if (deleteCount === addElements.length) return;
}
Copy the code

When the number of added elements is less than that of deleted elements, as shown in the figure:

const movePostElements = (array, startIndex, len, deleteCount, addElements) = > {
  / /...
  // If the number of elements added is not equal to the number of elements removed, the following element is moved
  if(deleteCount > addElements.length) {
    // There are more elements to delete than to add, so all the following elements move forward
    // Move len-startindex-deletecount altogether
    for (let i = startIndex + deleteCount; i < len; i++) {
      let fromIndex = i;
      // The target position to move to
      let toIndex = i - (deleteCount - addElements.length);
      if (fromIndex in array) {
        array[toIndex] = array[fromIndex];
      } else {
        deletearray[toIndex]; }}// Pay attention! In this case, we are moving the elements forward, which is equivalent to reducing the size of the array. We need to remove the redundant elements
    // The current length is len + addElements -deletecount
    for (let i = len - 1; i >= len + addElements.length - deleteCount; i --) {
      deletearray[i]; }}};Copy the code

When the number of added elements is greater than the number of deleted elements, as shown in the figure:

const movePostElements = (array, startIndex, len, deleteCount, addElements) = > {
  / /...
  if(deleteCount < addElements.length) {
    // The number of deleted elements is less than the number of new elements
    // Think about it: why do I traverse backwards here? What problems will arise from the past and the future?
    for (let i = len - 1; i >= startIndex + deleteCount; i--) {
      let fromIndex = i;
      // The target position to move to
      let toIndex = i + (addElements.length - deleteCount);
      if (fromIndex in array) {
        array[toIndex] = array[fromIndex];
      } else {
        deletearray[toIndex]; }}}};Copy the code

Optimization 1: boundary cases of parameters

We need to do something special when users send illegal startIndex and deleteCount or negative indexes.

const computeStartIndex = (startIndex, len) = > {
  // Handle negative index cases
  if (startIndex < 0) {
    return startIndex + len > 0 ? startIndex + len: 0;
  } 
  return startIndex >= len ? len: startIndex;
}

const computeDeleteCount = (startIndex, len, deleteCount, argumentsLen) = > {
  // Delete startIndex and all the following by default
  if (argumentsLen === 1) 
    return len - startIndex;
  // The number of deletions is too small
  if (deleteCount < 0) 
    return 0;
  // The number of deletions is too large
  if (deleteCount > len - deleteCount) 
    return len - startIndex;
  return deleteCount;
}

Array.prototype.splice = function (startIndex, deleteCount, ... addElements) {
  / /,...
  let deleteArr = new Array(deleteCount);
  
  // Clean the following parameters
  startIndex = computeStartIndex(startIndex, len);
  deleteCount = computeDeleteCount(startIndex, len, deleteCount, argumentsLen);
   
  // Copy the deleted element
  sliceDeleteElements(array, startIndex, deleteCount, deleteArr);
  / /...
}
Copy the code

Optimization 2: Array is sealed or frozen

What is a sealed object?

The sealed object is a non-extensible object, and the [[signals]] property of the existing member is set to false, which means that no additional methods and attributes can be added or deleted. But attribute values can be modified.

What is a frozen object?

Frozen objects are the strictest tamper-proof level, and in addition to containing restrictions on sealed objects, attribute values cannot be modified.

Now, let’s rule out both of these cases.

// Determine sealed objects and frozen objects
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! ')}Copy the code

Now we have a complete splice, like this:

const sliceDeleteElements = (array, startIndex, deleteCount, deleteArr) = > {
  for (let i = 0; i < deleteCount; i++) {
    let index = startIndex + i;
    if (index in array) {
      letcurrent = array[index]; deleteArr[i] = current; }}};const movePostElements = (array, startIndex, len, deleteCount, addElements) = > {
  // If the number of elements added is the same as the number of elements removed, it is equivalent to the replacement of elements. The array length remains the same, and the element after the deleted element does not need to be moved
  if (deleteCount === addElements.length) return;
  // If the number of elements added is not equal to the number of elements removed, the following element is moved
  else if(deleteCount > addElements.length) {
    // There are more elements to delete than to add, so all the following elements move forward
    // Move len-startindex-deletecount altogether
    for (let i = startIndex + deleteCount; i < len; i++) {
      let fromIndex = i;
      // The target position to move to
      let toIndex = i - (deleteCount - addElements.length);
      if (fromIndex in array) {
        array[toIndex] = array[fromIndex];
      } else {
        deletearray[toIndex]; }}// Pay attention! In this case, we are moving the elements forward, which is equivalent to reducing the size of the array. We need to remove the redundant elements
    // The current length is len + addElements -deletecount
    for (let i = len - 1; i >= len + addElements.length - deleteCount; i --) {
      deletearray[i]; }}else if(deleteCount < addElements.length) {
    // The number of deleted elements is less than the number of new elements
    // Think about it: why do I traverse backwards here? What problems will arise from the past and the future?
    for (let i = len - 1; i >= startIndex + deleteCount; i--) {
      let fromIndex = i;
      // The target position to move to
      let toIndex = i + (addElements.length - deleteCount);
      if (fromIndex in array) {
        array[toIndex] = array[fromIndex];
      } else {
        deletearray[toIndex]; }}}};const computeStartIndex = (startIndex, len) = > {
  // Handle negative index cases
  if (startIndex < 0) {
    return startIndex + len > 0 ? startIndex + len: 0;
  } 
  return startIndex >= len ? len: startIndex;
}

const computeDeleteCount = (startIndex, len, deleteCount, argumentsLen) = > {
  // Delete startIndex and all the following by default
  if (argumentsLen === 1) 
    return len - startIndex;
  // The number of deletions is too small
  if (deleteCount < 0) 
    return 0;
  // The number of deletions is too large
  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);

  // Determine sealed objects and frozen objects
  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! ')}// Copy the deleted element
  sliceDeleteElements(array, startIndex, deleteCount, deleteArr);
  // Move the element after the deleted element
  movePostElements(array, startIndex, len, deleteCount, addElements);

  // Insert a new element
  for (let i = 0; i < addElements.length; i++) {
    array[startIndex + i] = addElements[i];
  }

  array.length = len - deleteCount + addElements.length;

  return deleteArr;
}
Copy the code

The above code was tested against all test cases in the MDN documentation.

For the test code, go to: Portal

Finally, I present the V8 source code for you to check: V8 array splice source code line 660