Keep the feel of the code

preface

Slice is an API that is often used in JS. We have not studied how it is implemented in detail before, so I still want to take it for further study and deepen my impression.

The learning process is mainly divided into the following steps:

  1. According to MDN documentation description/own use experience, step by step manual version
  2. Refer to MDN to run all cases
  3. Reference ECMA specification implementation again
  4. Compare the differences, think and summarize

Basis function

Start by implementing a version of the basic functionality

// + basic functions
Array.prototype.mySlice = function(start, end) {
  const array = this;
  const newArray = [];
  for(let i=start; i<end; i++) {
      newArray.push(array[i]);
  }
  return newArray;
}

function testCase() {
   var fruits = ['Banana'.'Orange'.'Lemon'.'Apple'.'Mango'];
   var citrus = fruits.mySlice(1.3);
   console.log('mySlice citrus', citrus);
   citrus = fruits.slice(1.3);
   console.log('slice citrus', citrus);
}

testCase();

Copy the code

OUTPUT

// OUTPUT
"mySlice citrus" // [object Array] (2)
["Orange"."Lemon"]

"slice citrus" // [object Array] (2)
["Orange"."Lemon"]
Copy the code

The basic function implementation is relatively simple, here will not add explanation

Outlier handling

The above basic functions do not handle outliers. Here I refer to the description of MDN and my own understanding and add the following

  • If end is omitted, slice extracts all the way to the end of the original array.
  • If end is greater than the length of the array, Slice will extract all the way to the end of the array.
  • String and Boolean can be converted to Number
// Basic functions
// + If end is omitted, slice extracts until the end of the array.
// + If end is greater than the length of the array, slice will also extract to the end of the array.
String and Boolean can be converted to Number
Array.prototype.mySlice = function(start, end) {
  const array = this;
  const arrayLength = array.length;
  const newArray = [];
  start = Number(start), end = Number(end);
  
  if(! end || end > arrayLength) { end = arrayLength; }for(let i=start; i<end; i++) {
      newArray.push(array[i]);
  }
  return newArray;
}

function testCase() {
   var fruits = ['Banana'.'Orange'.'Lemon'.'Apple'.'Mango'];
   var citrusEndBig = fruits.mySlice(1.9);
   console.log('mySlice citrusEndBig', citrusEndBig);
   var citrusEndNull = fruits.mySlice(1);
   console.log('mySlice citrusEndNull', citrusEndNull);
   var citrusString = fruits.mySlice('1'.'9');
   console.log('mySlice citrusString', citrusString);
   var citrusBoolean = fruits.mySlice(false.true);
   console.log('mySlice citrusBoolean', citrusBoolean);
   var citrusObject = fruits.mySlice({"a":"b"}, {});
   console.log('mySlice citrusObject', citrusObject);  
  
   citrusEndBig = fruits.slice(1.9);
   console.log('slice citrusEndBig', citrusEndBig);
   citrusEndNull = fruits.slice(1);
   console.log('slice citrusEndNull', citrusEndNull);
   citrusString = fruits.slice('1'.'9');
   console.log('mySlice citrusString', citrusString);
   citrusBoolean = fruits.slice(false.true);
   console.log('mySlice citrusBoolean', citrusBoolean);  
   var citrusObject = fruits.slice({"a":"b"}, {});
   console.log('mySlice citrusObject', citrusObject);    
  
}

testCase();
Copy the code

OUTPUT

"mySlice citrusEndBig" // [object Array] (4)
["Orange"."Lemon"."Apple"."Mango"]
"mySlice citrusEndNull" // [object Array] (4)
["Orange"."Lemon"."Apple"."Mango"]
"mySlice citrusString" // [object Array] (4)
["Orange"."Lemon"."Apple"."Mango"]
"mySlice citrusBoolean" // [object Array] (1)
["Banana"]
"mySlice citrusObject" // [object Array] (0)
[]
"slice citrusEndBig" // [object Array] (4)
["Orange"."Lemon"."Apple"."Mango"]
"slice citrusEndNull" // [object Array] (4)
["Orange"."Lemon"."Apple"."Mango"]
"mySlice citrusString" // [object Array] (4)
["Orange"."Lemon"."Apple"."Mango"]
"mySlice citrusBoolean" // [object Array] (1)
["Banana"]
"mySlice citrusObject" // [object Array] (0)
[]
Copy the code

The value of “Number” is “NaN” and “Number” is “NaN”

Support for negative parameters

As we all know, slice supports negative arguments. Here let’s look at the description of MDN and think about how to implement it better

  • If begin is negative, the extract starts from the penultimate element of the array, and slice(-2) extracts the penultimate element of the array to the last element (including the last element).
  • If end is negative, it indicates the penultimate element in the original array at the end of the extraction. Slice (-2,-1) extracts the penultimate element of the array to the last element (excluding the last element, i.e. only the penultimate element).

It’s pretty much the same as it used to be, so essentially you start at the end of the array to find the index position, so let’s do the length of the array

// Basic functions
// If end is omitted, slice extracts all the way to the end of the array.
// If end is greater than the length of the array, slice will also extract to the end of the array.
String and Boolean can be converted to Number
// + If begin is negative, extract from the penultimate element of the array, slice(-2) extract from the penultimate element of the array to the last element (including the last element).
// + If end is negative, it indicates the penultimate element in the original array to end the extraction. Slice (-2,-1) extracts the penultimate element of the array to the last element (excluding the last element, i.e. only the penultimate element).
Array.prototype.mySlice = function(start, end) {
  const array = this;
  const arrayLength = array.length;
  const newArray = [];
  start = Number(start), end = Number(end);
  start = start < 0? arrayLength + start : start; end = ! end || end > arrayLength ? arrayLength : end end = end <0 ?  arrayLength + end : end;
  
  for(let i=start; i<end; i++) {
      newArray.push(array[i]);
  }
  return newArray;
}

function testCase() {
   var fruits = ['Banana'.'Orange'.'Lemon'.'Apple'.'Mango'];
   var citrus = fruits.mySlice(-3, -1);
   console.log('mySlice citrus', citrus);
   var citrus2 = fruits.mySlice(0, -1);
   console.log('mySlice citrus2', citrus2);  
  
   citrus = fruits.slice(-3, -1);
   console.log('slice citrus', citrus);
   citrus2 = fruits.slice(0, -1);
   console.log('slice citrus2', citrus2);  
}

testCase();
Copy the code

OUTPUT

"mySlice citrus" // [object Array] (2)
["Lemon"."Apple"]
"mySlice citrus2" // [object Array] (4)
["Banana"."Orange"."Lemon"."Apple"]
"slice citrus" // [object Array] (2)
["Lemon"."Apple"]
"slice citrus2" // [object Array] (4)
["Banana"."Orange"."Lemon"."Apple"]
Copy the code

The core is arrayLength + start, which is pretty simple

The element object is updated synchronously

So far, the core functions of Slice are almost complete. Since my array element assignment is a reference assignment, which theoretically supports object update, I will directly test the CASE on MDN. Let’s first look at the description of MDN

  • If the element is an object reference (not an actual object), Slice copies the object reference into the new array. Both object references refer to the same object. If the referenced object changes, the element in the new and original array changes as well.
function testCase() {
  // Create a newCar from myCar using the slice method.
  var myHonda = { color: 'red'.wheels: 4.engine: { cylinders: 4.size: 2.2}};var myCar = [myHonda, 2."cherry condition"."purchased 1997"];
  var newCar = myCar.slice(0.2);

  // Outputs the color attribute referenced by myCar, newCar, and their respective myHonda objects.
  console.log(' myCar = ' + JSON.stringify(myCar));
  console.log('newCar = ' + JSON.stringify(newCar));
  console.log(' myCar[0].color = ' + JSON.stringify(myCar[0].color));
  console.log('newCar[0].color = ' + JSON.stringify(newCar[0].color));

  // Change the color attribute of the myHonda object.
  myHonda.color = 'purple';
  console.log('The new color of my Honda is ' + myHonda.color);

  // Outputs the color attribute referenced by the respective myHonda objects in myCar and newCar.
  console.log(' myCar[0].color = ' + myCar[0].color);
  console.log('newCar[0].color = ' + newCar[0].color);
}

testCase();
Copy the code

OUTPUT

"MyCar = [{' color ':' red ', 'wheels: 4,' engine ': {' cylinders' : 4,' the size: 2.2}}, 2, 'cherry condition', 'transition to 1997]." "
"NewCar = [{' color ':' red ', 'wheels: 4,' engine ': {' cylinders' : 4,' size ': 2.2}}, 2]"
" myCar[0].color = 'red'"
"newCar[0].color = 'red'"
"The new color of my Honda is purple"
" myCar[0].color = purple"
"newCar[0].color = purple"
Copy the code

The results on MDN are consistent with expectations

referenceECMASpecification implementation

Let’s take a look at the pseudocode for ECMA

A cursory look at most and their own implementation of the same, step by step to achieve a look

Array.prototype.mySlice = function(start, end) {
  // 1. Let A be a new array created as if by the expression new Array().
  const newArray = []; 
  // 2. Call the [[Get]] method of this object with argument "length". 
  // 3. Call ToUint32(Result(2))
  const arrayLength = Number(this.length); 
  // 4. Call ToInteger(start).
  start = Number(start); 
  // 5. If Result(4) is negative, use max((Result(3)+Result(4)),0); else use min(Result(4),Result(3)). 
  // 6. Let k be Result(5).
  let kStart = start < 0 ? Math.max(arrayLength + start, 0) : Math.min(start, arrayLength); 
  // 7. If end is undefined, use Result(3); else use ToInteger(end).end = ! end ? arrayLength :Number(end); 
  // 8. If Result(7) is negative, use max((Result(3)+Result(7)),0); else use min(Result(7),Result(3)).
  end = end < 0 ? Math.max(arrayLength + end, 0) : Math.min(end, arrayLength); 
  // 9. Let n be 0.
  let n = 0; 
  
  while(true) {
    // 10. If k is greater than or equal to Result(8), go to step 19.
    if( kStart >= end) {
      // 19. Call the [[Put]] method of A with arguments "length" and n.
      // 20. Return A.
      return newArray;
    }
    // 11. Call ToString(k).
    kStart = kStart.toString();
    // 12. If this object has a property named by Result(11), go to step 13; but if this object has no property named by Result(11), then go to step 16.
    if(this.hasOwnProperty(kStart)) {
      // 13. Call ToString(n).
      n = n.toString();
      // 14. Call the [[Get]] method of this object with argument Result(11).
      // 15. Call the [[Put]] method of A with arguments Result(13) and Result(14).
      this[n] = this[kStart];
    } 
    // 16. Increase k by 1.
    // 17. Increase n by 1.kStart++; n++; }}Copy the code

When I translated the pseudocode, I noticed two major differences

  1. In the specification, Max and min are used for processing. The advantage of this is that when the index position passed in exceeds the total length, it is automatically set to the maximum value, 0 for a negative number, and length for a positive number. This is not considered in my previous code, and this code is also very simple. Avoid writing a lot of if else
  let kStart = start < 0 ? Math.max(arrayLength + start, 0) : Math.min(start, arrayLength);
Copy the code
  1. The value of the input value is array-like, because the array-like key may be a string

The last

By walking through the code, you can gain a more comprehensive understanding of Slice, which is mainly learned in this example

  1. The standard pseudo code still has a lot of thinking very comprehensive place, and the realization is more concise, in the daily development can draw on some ideas
  2. Slice is very inclusive and supports multiple types of data sources and parameters, which is a double-edged sword just like JS itself. It is better to keep variables controllable during the development process for code maintainability

Welcome all kinds of clap brick, discussion