Por favor, use Typescript para escrever a declaração de 20 métodos de array

prefácio

Há algum tempo, assisti à transmissão ao vivo e vi o Tio Lobo refutar a "teoria de que o front end está morto". Não sei se o front end está morto ou não. De qualquer forma, o front end não pode receber tanto salário quanto antes; bem, vamos direto ao ponto, Tio Lobo mencionou na sala de transmissão ao vivo que o front end é obrigado a escrever 20 métodos no array.

Passo 1: Classifique

É um pouco difícil escrever 20 métodos de array de uma só vez. Podemos classificar os métodos de array em nossas mentes e agrupar o mesmo tipo de operações em uma categoria. Não é mais fácil escrever dessa maneira?

Adicionar classe de elemento: push, unshift
Excluir classe de elemento: pop, shift, splice
Array para classe de string: toString, join
Traversal class: forEach, reduce, reduceRight, map, filter, some, every
Sorting: sort
Splicing: concat
Index: indexOf, lastIndexOf

Escrevi um 19 completo de uma só vez, mas não é o suficiente para o 20. Parece que não estou qualificado para dizer "o front end está morto". Vamos verificar o que está errado:

Virar:
cópia rasa reversa: fatiar
por que escrever isso? Porque esses são métodos de array definidos em lib.es5.d.ts no vscode

Etapa 2: Implementar a interface do array
O array precisa receber um parâmetro genérico para obter dinamicamente o tipo de elemento no array

interface MyArray<T> {
    
    
  
}

Passo 3: Definição do Método

O primeiro é o método de classe de adição de elemento: push, unshift, não esqueça que eles têm um valor de retorno e o valor de retorno é o comprimento do novo array

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

Excluir método de classe de elemento, os dois primeiros são mais fáceis de escrever e seu valor de retorno é o elemento excluído, mas deve-se observar que a matriz vazia retorna indefinido após a chamada;

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

Ainda há um problema com a escrita de splice assim, porque apenas o primeiro parâmetro de splice é obrigatório, então você precisa escrever várias declarações

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

Depois, há a classe array-to-string: toString, join, escreva diretamente sem dificuldade

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

Traversal classes: forEach, reduce, reduceRight, map, filter, some, every Escrevemos um por um, o primeiro é o método forEach, neste método geralmente usamos apenas a função callback, mas na verdade existe outro parâmetro que pode especificar o this da função callback

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

O método de redução pode realizar o acumulador e também é um dos métodos mais usados. A diferença entre reduzirRight e reduzir é que ele percorre da direita para a esquerda

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

O método map percorre o array e retorna um novo array, é uma função pura

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

Não repetiremos os seguintes métodos de passagem. Basicamente, todos eles seguem a função de retorno de chamada, este parâmetro de ligação, este modo fixo

Alguns dos métodos a seguir são relativamente simples e, finalmente, as definições de método escritas são resumidas:

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[];
}

A frente é tudo um prelúdio, agora vamos começar o tópico de hoje, esses métodos de arrays de caligrafia.

A quarta etapa implementa esses métodos

Primeiro, modificamos o nome da definição da interface: IMyArray e, em seguida, definimos a classe MyArray para implementar a interface, e o editor injetará automaticamente o método acima

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;
};

Para as seguintes implementações, usamos arrays para implementá-los. Por exemplo, para implementar um dos métodos de travessia, implementamos outros mais complexos, como o reduce. A implementação do reduce é relativamente simples.

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;
};

Em seguida, implemente um método de inversão de matriz reversa, podemos percorrer a primeira metade dos dados e, em seguida, trocar com a segunda metade, respectivamente, completando assim a inversão no local:

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;
};

Quanto aos métodos sort e flat, existem muitas maneiras de implementá-los. Podemos consultar a documentação oficial do V8; na documentação podemos encontrar:
insira a descrição da imagem aqui

O método de classificação anterior era baseado em quicksort e era um algoritmo de classificação instável. Posteriormente, o V8 migrou a classificação para Torque. tq é uma DSL especial que usa o algoritmo Timsort para obter uma classificação estável. O Timsort pode ser considerado uma classificação de mesclagem estável

Resumo
Começamos com os 20 métodos da matriz, estudamos a definição ts e o uso desses métodos e os simulamos por escrito, e então entendemos seu princípio para o algoritmo de classificação mais complexo. Obviamente, o algoritmo de classificação não é mais o algoritmo de classificação instável implementado pelo quicksort. Agora é um algoritmo de classificação estável e é baseado na classificação por mesclagem, portanto, devemos dominar a classificação por mesclagem; além disso, vale a pena praticar esse método de aprendizado do superficial ao profundo;

Para o título, se o front-end está morto, acho que todos têm a resposta em seus próprios corações;

Acho que você gosta

Origin blog.csdn.net/qq_40850839/article/details/131803842
Recomendado
Clasificación