网站首页 > 技术文章 正文
前言
前段时间看直播看到狼叔直播驳斥”前端已死论“,前端死没死不知道,反正前端是拿不到以前那么多工资了;好,进入正题,狼叔在直播间提到要求前端写出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中定义的数组方法
第二步:实现数组接口
数组需要接收一个泛型参数,用来动态获取数组中元素类型
ts复制代码interface MyArray<T> {
}
第三步:方法定义
首先是元素添加类方法:push、unshift,千万不要忘了他们有返回值,返回值是新数组的length
ts复制代码 push(...args: T[]): number;
unshift(...args: T[]): number;
删除元素类方法,前两个比较好写,它们的返回值都是删除的那个元素,但是需要注意的是空数组调用后返回undefined;
ts复制代码 pop(): T | undefined;
shift(): T | undefined;
/**错误的写法:splice(start: number, deleteNum: number, ...args: T[]): T[];**/
splice这样写还有问题,因为splice只有第一个参数是必传,这样就需要写多个声明了
ts复制代码 splice(start: number, deleteNum?: number): T[];
splice(start: number, deleteNum: number, ...args: T[]): T[];
然后是数组转字符串类:toString、join,没有难度直接写
ts复制代码 join(param?: string): string;
toString(): string;
遍历类:forEach、reduce、reduceRight、map、filter、some、every 我们一个一个地来写,首先是forEach方法,这个方法我们常用的就只有回调函数,但是其实还有一个参数可以指定回调函数的this
ts复制代码forEach(callbackFn: (value: T, index: number, array: T[]) => void, thisArg?: any): void;
reduce这个方法可以实现累加器,也是我们最常用的方法之一,reduceRight与reduce的区别就在于它是从右往左遍历
ts复制代码 reduce(callbackFn: (previousValue: T, currentValue: T, currentIndex: number, array: T[]) => T, initialValue: T): T;
map方法遍历数组并且会返回一个新的数组,它是一个纯函数
ts复制代码 map(callbackFn: (value: T, index: number, array: T[]) => T, thisArg?: any): T[];
后面的一些遍历方法我们就不再赘述,基本上都遵从回调函数,this绑定参数,这种固定模式
后面的一些方法都比较简单,最后把写好的方法定义都汇总起来:
ts复制代码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类实现该接口,编辑器会自动将上面的方法注入
ts复制代码class MyArray<T> implements IMyArray<T> {
}
先实现push方法:
ts复制代码push(...args: T[]): number {
const len = args.length;
for (let i = 0; i < len; i++) {
this[this.length++] = args[i];
}
return this.length;
}
其实我们实现的是一个类数组,只不过含有数组的所有方法,这里经常会使用类数组来考察对push的理解,比如这道题:
js复制代码const obj = {
0:1,
3:2,
length:2,
push:[].push
}
obj.push(3);
然后实现一个splice,注意splice是一个原地修改数组的方法,所以我们不能借助额外的空间实现,这里我们还是使用Array.prototype.splice的方式来实现,类数组不能通过length属性删除元素
ts复制代码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的实现比较简单
ts复制代码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数组翻转方法,我们可以遍历前一半的数据,然后分别与后面一半进行交换,这样就完成了原地翻转:
ts复制代码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算法已经不是那个以前用快排实现的不稳定的排序算法了,现在是一种稳定的排序算法,并且基于归并排序,所以归并排序我们一定要掌握好;另外这种由浅入深的学习方法,值得大家去实践;
针对标题,前端有没有死,我想大家自己心里自有答案;
猜你喜欢
- 2024-09-30 JavaScript数组_数组方法「二」(二十七)
- 2024-09-30 table组件,前端如何使用table组件打印数组数据
- 2024-09-30 前端数组改字符串方法 前端数组改字符串方法是什么
- 2024-09-30 javascript复制数组的三种方式 javascript复制粘贴
- 2024-09-30 第21节 检测数组、类数组及多维数组-Web前端开发之Javascript
- 2024-09-30 前端系列——ES6中循环数组的方法
- 2024-09-30 springboot项目中,前端如何传递一个自定义对象数组给后端
- 2024-09-30 带你走进javascript数组的世界 javascript数组操作方法
- 2024-09-30 每天学点 ES6 —— 数组(二) es6数组处理方法
- 2024-09-30 ES6新特性系列之数组解构赋值 es6数组函数
你 发表评论:
欢迎- 最近发表
-
- 前端流行框架Vue3教程:13. 组件传递数据_Props
- 前端必看!10 个 Vue3 救命技巧,解决你 90% 的开发难题?
- JAVA和JavaScript到底是什么关系?是亲戚吗?
- Java和js有什么区别?(java和javascript的区别和联系)
- 东方标准|Web和Java的区别,如何选择这两个专业
- 前端面试题-JS 中如何实现大对象深度对比
- 360前端一面~面试题解析(360前端笔试)
- 加班秃头别慌!1 道 Vue 面试题,快速解锁大厂 offer 通关密码
- 焦虑深夜刷题!5 道高频 React 面试题,吃透 offer 稳了
- 2025Web前端面试题大全(整理版)面试题附答案详解,最全面详细
- 标签列表
-
- 前端设计模式 (75)
- 前端性能优化 (51)
- 前端模板 (66)
- 前端跨域 (52)
- 前端md5加密 (49)
- 前端路由 (55)
- 前端数组 (65)
- 前端定时器 (47)
- 前端懒加载 (45)
- 前端接口 (46)
- Oracle RAC (73)
- oracle恢复 (76)
- oracle 删除表 (48)
- oracle 用户名 (74)
- oracle 工具 (55)
- oracle 内存 (50)
- oracle 导出表 (57)
- oracle查询数据库 (45)
- oracle约束 (46)
- oracle 中文 (51)
- oracle链接 (47)
- oracle的函数 (57)
- mac oracle (47)
- 前端调试 (52)
- 前端登录页面 (48)
本文暂时没有评论,来添加一个吧(●'◡'●)