Please use Typescript to write the declaration of 20 array methods

foreword

Some time ago, I watched the live broadcast and saw Uncle Wolf refute the "theory that the front end is dead". I don't know if the front end is dead or not. Anyway, the front end can't get as much salary as before; well, let's get to the point, Uncle Wolf mentioned in the live broadcast room that the front end is required to write 20 methods on the array.

Step 1: Classify

It is a bit difficult to write 20 array methods in one breath. We can classify the array methods in our minds, and group the same type of operations into one category. Isn't it easier to write this way?

Add element class: push, unshift
Delete element class: pop, shift, splice
Array to string class: toString, join
Traversal class: forEach, reduce, reduceRight, map, filter, some, every
Sorting: sort
Splicing: concat
Index: indexOf, lastIndexOf

I wrote a full 19 in one breath, but it is not enough for the 20. It seems that I am not qualified to say "the front end is dead". Let's check what is wrong:

Flip: reverse
shallow copy: slice
why write these? Because these are array methods defined in lib.es5.d.ts in vscode

Step 2: Implement the array interface
The array needs to receive a generic parameter to dynamically obtain the element type in the array

interface MyArray<T> {
    
    
  
}

Step 3: Method Definition

The first is the element adding class method: push, unshift, don't forget that they have a return value, and the return value is the length of the new array

push(...args: T[]): number;
unshift(...args: T[]): number;

Delete element class method, the first two are easier to write, and their return value is the deleted element, but it should be noted that the empty array returns undefined after calling;

pop(): T | undefined;
shift(): T | undefined;
// 错误的写法:splice(start: number, deleteNum: number, ...args: T[]): T[];

There is still a problem with splice writing like this, because only the first parameter of splice is mandatory, so you need to write multiple declarations

splice(start: number, deleteNum?: number): T[];
splice(start: number, deleteNum: number, ...args: T[]): T[];

Then there is the array-to-string class: toString, join, write directly without difficulty

join(param?: string): string;
toString(): string;

Traversal classes: forEach, reduce, reduceRight, map, filter, some, every We write one by one, the first is the forEach method, this method we often only use the callback function, but in fact there is another parameter that can specify the this of the callback function

forEach(callbackFn: (value: T, index: number, array: T[]) => void, thisArg?: any): void;

The reduce method can realize the accumulator, and it is also one of our most commonly used methods. The difference between reduceRight and reduce is that it traverses from right to left

reduce(callbackFn: (previousValue: T, currentValue: T, currentIndex: number, array: T[]) => T, initialValue: T): T;

The map method traverses the array and returns a new array, it is a pure function

map(callbackFn: (value: T, index: number, array: T[]) => T, thisArg?: any): T[];

We won’t repeat the following traversal methods. Basically, they all follow the callback function, this binding parameter, this fixed mode

Some of the following methods are relatively simple, and finally the written method definitions are summarized:

interface MyArray<T> {
    
    
  length: number;
  // 数组添加元素
  push(...args: T[]): number;
  unshift(...args: T[]): number;

  // 数组删除元素
  pop(): T | undefined;
  shift(): T | undefined;
  splice(start?: number, deleteNum?: number): T[];
  splice(start: number, deleteNum?: number): T[];
  splice(start: number, deleteNum: number, ...args: T[]): T[];

  // 数组索引
  indexOf(item: T): number;
  lastIndexOf(item: T): number;

  // 数组遍历
  forEach(callbackFn: (value: T, index: number, array: T[]) => void, thisArg?: any): void;
  reduce(callbackFn: (previousValue: T, currentValue: T, currentIndex: number, array: T[]) => T, initialValue: T): T;
  reduceRight(callbackFn: (previousValue: T, currentValue: T, currentIndex: number, array: T[]) => T): T;
  some(callbackFn: (value: T, index: number, array: T[]) => boolean, thisArg?: any): boolean;
  every(callbackFn: (value: T, index: number, array: T[]) => boolean, thisArg?: any): boolean;
  map(callbackFn: (value: T, index: number, array: T[]) => T, thisArg?: any): T[];

  //   数组与字符串
  join(param?: string): string;
  toString(): string;
  toLocalString(): string;

  //   数组排序
  sort(callbackFn: (a: T, b: T) => number): T[];

  // 数组扁平化
  flat(deepSize: number): T[];

  //   数组的拼接
  concat(...args: T[]): T[];

  //   数组的拷贝
  slice(start?: number, end?: number): T[];

  //   数组翻转
  reverse(): T[];
}

The front is all a prelude, now let’s start today’s topic, these methods of handwriting arrays.

The fourth step implements these methods

First, we modify the name of the interface definition: IMyArray, and then define the MyArray class to implement the interface, and the editor will automatically inject the above method

class MyArray<T> implements IMyArray<T> {
    
    
}
先实现push方法:

push(...args: T[]): number {
    
    
 const len = args.length;
 for (let i = 0; i < len; i++) {
    
    
   this[this.length++] = args[i];
 }
 return this.length;
}
其实我们实现的是一个类数组,只不过含有数组的所有方法,这里经常会使用类数组来考察对push的理解,比如这道题:

const obj = {
    
     
 0:1, 
 3:2, 
 length:2, 
 push:[].push 
} 
obj.push(3);
然后实现一个splice,注意splice是一个原地修改数组的方法,所以我们不能借助额外的空间实现,这里我们还是使用Array.prototype.splice的方式来实现,类数组不能通过length属性删除元素

Array.prototype.splice = function splice(start: number, deleteNum = 1, ...rest: any[]) {
    
    
  if (start === undefined) {
    
    
    return [];
  }

  const that = this;
  let returnValue: any[] = [];
  // 将begin到end的元素全部往前移动
  function moveAhead(begin: number, end: number, step: number) {
    
    
    const deleteArr: any[] = [];
    // 可以从前往后遍历
    for (let i = begin; i < end && i + step < end; i++) {
    
    
      if (i < begin + step) {
    
    
        deleteArr.push(that[i]);
      }
      that[i] = that[i + step];
    }
    return deleteArr;
  }
  function pushAtIdx(idx: number, ...items: any[]) {
    
    
    const len = items.length;
    const lenAfter = that.length;
    // 在idx处添加len个元素,首先需要把所有元素后移len位,然后替换中间那些元素
    for (let i = idx; i < idx + len; i++) {
    
    
      if (i < lenAfter) {
    
    
        that[i + len] = that[i];
      }

      if (i - idx < len) {
    
    
        that[i] = items[i - idx];
      }
    }
  }
  if (deleteNum >= 1) {
    
    
    returnValue = moveAhead(Math.max(start, 0), that.length, deleteNum);
    that.length -= deleteNum;
  }

  pushAtIdx(start, ...rest);
  return returnValue;
};

For the following implementations, we use arrays to implement them. For example, to implement one of the traversal methods, we implement more complex ones such as reduce. The implementation of reduce is relatively simple.

Array.prototype.reduce = function <T>(
  callbackFn: (previousValue: T, currentValue: T, currentIndex: number, array: T[]) => T,
  initialValue?: T
): T {
    
    
  // reduce如果有初始值那么以初始值开始计算,如果没有初始值那么用数组第一项作为初始值
  let startIndex = 0;
  const len = this.length;
  let ans = initialValue;
  if (initialValue === undefined) {
    
    
    ans = this[0];
    startIndex = 1;
  }

  for (let i = startIndex; i < len; i++) {
    
    
    ans = callbackFn(ans, this[i], i, this);
  }
  return ans;
};

Then implement a reverse array flip method, we can traverse the first half of the data, and then exchange with the second half respectively, thus completing the in-place flip:

Array.prototype.reverse = function () {
    
    
 const len = this.length;
 const that = this;
 function swap(a, b) {
    
    
   const tmp = that[a];
   that[a] = that[b];
   that[b] = tmp;
 }
 for (let i = 0; i < len >> 1; i++) {
    
    
   swap(i, len - i - 1);
 }
 return this;
};

As for the sort and flat methods, there are many ways to implement them. We can refer to the official documentation of V8; from the documentation we can find:
insert image description here

The previous sort method was based on quicksort, and it was an unstable sorting algorithm. Later, V8 migrated sort to Torque. tq is a special DSL that uses the Timsort algorithm to achieve stable sorting. Timsort can be regarded as a stable merge sort

Summary
We start with the 20 methods of the array, study the ts definition and usage of these methods, and simulate them by handwriting, and then we understand its principle for the more complex sort algorithm. Obviously, the sort algorithm is no longer the unstable sorting algorithm implemented by quicksort. Now it is a stable sorting algorithm, and it is based on merge sorting, so we must master merge sorting; in addition, this learning method from shallow to deep is worth practicing;

For the title, whether the front end is dead, I think everyone has the answer in their own hearts;

Guess you like

Origin blog.csdn.net/qq_40850839/article/details/131803842