请用Typescript写出20个数组方法的声明

news2024/11/25 20:45:43

前言

前段时间看直播看到狼叔直播驳斥”前端已死论“,前端死没死不知道,反正前端是拿不到以前那么多工资了;好,进入正题,狼叔在直播间提到要求前端写出20个数组上的方法,这确实不太简单,但是只写出方法没有什么意义,我们今天来写20个数组方法的声明;这要求我们对于每一个方法的每一个参数用法都了解透彻。

第一步:分门别类

一口气写出20个数组方法有点难度,我们可以在脑海里对数组方法进行分类,同一类操作归为一类,这样写是不是更加简单了呢?

添加元素类:push、unshift
删除元素类:pop、shift、splice
数组转字符串类:toString、join
遍历类:forEach、reduce、reduceRight、map、filter、some、every
排序:sort
拼接:concat
索引:indexOf、lastIndexOf

一口气写了整整19个,就是不够那20个,看来我不够资格说”前端已死“,来查一查差哪些:

翻转:reverse
浅拷贝:slice
为什么写这些?因为这些是vscode中lib.es5.d.ts中定义的数组方法

第二步:实现数组接口
数组需要接收一个泛型参数,用来动态获取数组中元素类型

interface MyArray<T> {
  
}

第三步:方法定义

首先是元素添加类方法:push、unshift,千万不要忘了他们有返回值,返回值是新数组的length

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

删除元素类方法,前两个比较好写,它们的返回值都是删除的那个元素,但是需要注意的是空数组调用后返回undefined;

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

splice这样写还有问题,因为splice只有第一个参数是必传,这样就需要写多个声明了

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

然后是数组转字符串类:toString、join,没有难度直接写

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

遍历类:forEach、reduce、reduceRight、map、filter、some、every 我们一个一个地来写,首先是forEach方法,这个方法我们常用的就只有回调函数,但是其实还有一个参数可以指定回调函数的this

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

reduce这个方法可以实现累加器,也是我们最常用的方法之一,reduceRight与reduce的区别就在于它是从右往左遍历

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

map方法遍历数组并且会返回一个新的数组,它是一个纯函数

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

后面的一些遍历方法我们就不再赘述,基本上都遵从回调函数,this绑定参数,这种固定模式

后面的一些方法都比较简单,最后把写好的方法定义都汇总起来:

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

前面都是前奏,现在开始今天的正题,手写数组的这些方法。

第四步 实现这些方法

首先我们修改一下接口定义的名称:IMyArray,然后定义MyArray类实现该接口,编辑器会自动将上面的方法注入

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

后面的实现我们都是用数组来实现,比如实现其中某一个遍历的方法,我们就实现比较复杂的比如reduce,reduce的实现比较简单

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

然后再实现一个reverse数组翻转方法,我们可以遍历前一半的数据,然后分别与后面一半进行交换,这样就完成了原地翻转:

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

至于sort和flat方法这些都有很多实现方式,我们可以参考一下V8官方的文档;从文档中我们可以发现:
在这里插入图片描述

之前的sort方法是基于快排,并且是一种不稳定的排序算法,后来V8将sort迁移到了Torque,tq是一种特殊的DSL,利用Timsort算法实现了稳定的排序,Timsort可以看成一种稳定的归并排序

总结
我们先从数组的20个方法为切入点,研究了这些方法的ts定义,用法,顺便手写模拟了一下它们,然后对于比较复杂的sort算法我们了解了一下它的原理,显然sort算法已经不是那个以前用快排实现的不稳定的排序算法了,现在是一种稳定的排序算法,并且基于归并排序,所以归并排序我们一定要掌握好;另外这种由浅入深的学习方法,值得大家去实践;

针对标题,前端有没有死,我想大家自己心里自有答案;

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/778666.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【CSharp】关于xxx.csproj文件的理解

【CSharp】关于xxx.csproj文件的理解 1、背景2 关于.csproj 文件 1、背景 CShape又简写C#。 在示例代码里&#xff0c;遇到.csproj 文件。 项目结构如下&#xff1a; 本博客属于小白入门级。 2 关于.csproj 文件 上面的iRayBase.csproj 文件后缀是 .csproj 。 csproj的全称…

框架漏洞-CVE复现-Apache Shiro+Apache Solr

什么是框架&#xff1f; 就是别人写好包装起来的一套工具&#xff0c;把你原先必须要写的&#xff0c;必须要做的一些复杂的东西都写好了放在那里&#xff0c;你只要调用他的方法&#xff0c;就可以实现一些本来要费好大劲的功能。 如果网站的功能是采用框架开发的&#xff0c;…

typescript自动编译文件实时更新

npm install -g typescripttsc --init 生成tsconfig.json配置文件 tsc -w 在监听模式下运行&#xff0c;当文件发生改变的时候自动编译

【数学建模快速入门】

MD5码 生成了MD5码之后就不可以再去碰文件了&#xff08;打开都不行&#xff09;百度搜索 1、查询词的外边加上双引号“” 2、在查询词的前面加上&#xff1a;intitle: 3、查询词后面加上空格再输入filetype&#xff1a;文件格式&#xff08;doc/pdf/xls&#xff09; 4、在3的…

React+Redux 数据存储持久化

ReactRedux 数据存储持久化 1、安装相关依赖 yarn add reduxjs/toolkit redux redux-persist 2、userSlice&#xff1a;用户状态数据切片封装 import { createSlice, PayloadAction } from reduxjs/toolkitinterface IUserInfo {userName: stringavatar?: stringbrief?: st…

第111天:免杀对抗-JavaASM汇编CS调用内联CMSF源码特征修改Jar打包

知识点 #知识点&#xff1a; 1、ASM-CS-单汇编&内联C 2、JAVA-MSF-源码修改&打包#章节点&#xff1a; 编译代码面-ShellCode-混淆 编译代码面-编辑执行器-编写 编译代码面-分离加载器-编写 程序文件面-特征码定位-修改 程序文件面-加壳花指令-资源 代码加载面-Dll反射…

基于linux下的高并发服务器开发(第三章)- 3.6 线程取消

#include <pthread.h> int pthread_cancel(pthread_t thread);- 功能&#xff1a;取消线程&#xff08;让线程终止&#xff09;取消某个线程&#xff0c;可以终止某个线程的运行&#xff0c;但是并不是立马终止&#xff0c;而是当子线程执行到一个取消点&#xff0c;线程…

GOT Online|解密游戏性能优化秘籍

随着UWA GOT Online功能的不断迭代&#xff0c;GOT Online为解决各种游戏性能问题&#xff08;如内存占用、CPU耗时、GPU耗时和卡顿&#xff09;提供了丰富的高效、准确且便捷的数据获取方式和分析建议。本文总结了GOT Online&#xff08;SDK 2.4.7版本&#xff09;中的关键优化…

fps php,帧率60帧是什么意思

帧率60的意思是每秒屏幕刷新60次&#xff0c;帧率是用于测量显示帧数的量度。所谓的测量单位为每秒显示帧数即Frames per Second&#xff0c;简称FPS或“赫兹”&#xff0c;此词多用于影视制作和电子游戏。 本文操作环境&#xff1a;Windows7系统&#xff0c;Dell G3电脑。 帧…

计算机网络模型

计算机网络模型 网络模型网络模型中各层对应的协议封装与分用TCP/IP协议簇的组成 网络模型 OSI 七层模型 应用层、表示层、会话层、传输层、网络层、数据链路层、物理层 TCP/IP四层模型 应用层、传输层、网络层、网络接口层 TCP/IP五层模型 应用层、传输层、网络层、数据链路…

【SpringCloud Alibaba】(二)微服务环境搭建

1. 项目流程搭建 整个项目主要分为 用户微服务、商品微服务和订单微服务&#xff0c;整个过程模拟的是用户下单扣减库存的操作。这里&#xff0c;为了简化整个流程&#xff0c;将商品的库存信息保存到了商品数据表&#xff0c;同时&#xff0c;使用商品微服务来扣减库存。小伙…

【简单认识MySQL主从复制与读写分离】

文章目录 一、MySQL主从复制1、配置主从复制的原因&#xff1a;2、主从复制原理1、 MySQL的复制类型2、 MySQL主从复制的工作过程;1、 MySQL主从复制延迟2、优化方案&#xff1a;3、 MySQL 有几种同步方式&#xff1a; 三种4、异步复制&#xff08;Async Replication&#xff0…

【初始C语言】多种输入格式的优劣

多种输入格式的优劣【初始C语言】 一.多种输入格式的不同&#xff08;只针对输入字符&#xff0c;字符串&#xff09;1.scanf&#xff08;"%s",字符数组名&#xff09;2.scanf("%[^\n]s",字符数组名)3.gets(字符数组名)4.fgets&#xff08;字符数组名,规定…

光耦参数设置

2.1发光二极管电阻的选择 数据手册中&#xff0c;IF是发光二极管的允许最大正向电流&#xff0c;值是80mA。这是需要考虑的第一个条件。 再看第二张图&#xff0c;VF是二极管上的电压&#xff0c;图中给出&#xff0c;当IF是10mA的时候&#xff0c;VF的值最大是1.4V。这是需要考…

【python工具】html中表格转化为excel

背景 大家在实际的工作中可能会遇到这样的场景,查看某个统计的页面数据,其中一些数据是表格形式展示的,比如这是国家统计局关于人口统计的数据: 你想将表格内容下载下来根据自己的需要进行二次加工,但是页面没有提供下载功能或者需要你登陆才能下载。那么重点来了~~ 操…

初识 Spring (存储和获取 bean)

目录 初识 Spring总结 DI&#xff08;依赖注入&#xff09;Spring 项目的创建创建一个 Maven 项目添加 Spring 框架支持添加启动类 存储 bean 对象创建 bean将 bean 注册到容器中 获取并使用 bean 对象获取 bean 的方法一获取 bean 的方法二获取 bean 的方法三 ApplicationCont…

【雕爷学编程】Arduino动手做(55)--DHT11温湿度传感器模块2

37款传感器与执行器的提法&#xff0c;在网络上广泛流传&#xff0c;其实Arduino能够兼容的传感器模块肯定是不止这37种的。鉴于本人手头积累了一些传感器和执行器模块&#xff0c;依照实践出真知&#xff08;一定要动手做&#xff09;的理念&#xff0c;以学习和交流为目的&am…

Cesium态势标绘专题-位置点(标绘+编辑)

标绘专题介绍:态势标绘专题介绍_总要学点什么的博客-CSDN博客 入口文件:Cesium态势标绘专题-入口_总要学点什么的博客-CSDN博客 辅助文件:Cesium态势标绘专题-辅助文件_总要学点什么的博客-CSDN博客 本专题没有废话,只有代码,代码中涉及到的引入文件方法,从上面三个链…

RocketMQ教程-(4)-领域模型-消费者分组ConsumerGroup

定义​ 消费者分组是 Apache RocketMQ 系统中承载多个消费行为一致的消费者的负载均衡分组。 和消费者不同&#xff0c;消费者分组并不是运行实体&#xff0c;而是一个逻辑资源。在 Apache RocketMQ 中&#xff0c;通过消费者分组内初始化多个消费者实现消费性能的水平扩展以…

练习——动态内存分配的笔试题

今天我们分享几道经典的笔试题&#xff0c;做完直接变成陈泽 第一题 ~~ --------------------------------------------------------------------------------------------------~~ void GetMemory(char* p) {p (char*)malloc(100); } void Test(void) {char* str NULL;Get…