结合编辑器(ws)给出的数组类型声明,对数组上的方法、属性进行一波总结学习。文章最后有每个方法的描述和示例
1. 会修改原数组的数组方法
因为vue在实现数组的响应式的时候,修改了数组的七个方法,所以我刚开始以为会修改数组的方法只有七个。但是完整学习下来,其实是不止的,一共有九个。es2015新增了两个方法:copyWithin和fill。
// 在数组的末尾进行操作
pop(): T | undefined;
push(...items: T[]): number;
// 在数组的头部进行操作
shift(): T | undefined;
unshift(...items: T[]): number;
// 在数组的任意位置添加,删除元素
splice(start: number, deleteCount: number, ...items: T[]): T[];
// 数组间元素拷贝
copyWithin(target: number, start: number, end?: number): this;
// 数组倒序
reverse(): T[];
// 数组排序
sort(compareFn?: (a: T, b: T) => number): this;
// 数组填值
fill(value: T, start?: number, end?: number): this
2. 返回值和原数组严格相等的数组方法
曾有这样一个不正确的想法,不知道你们会不会也曾有。数组的所有方法,如果函数执行之后不会修改原数组,则一定会返回操作后新数组(例如map)反之,如果修改了原数组,则返回值一定不会是原数组(例如splice)。实践之后证明这个想法是错误的,下面这四个方法执行之后的将返回原数组的引用。
// 数组倒序
reverse(): T[];
// 数组排序
sort(compareFn?: (a: T, b: T) => number): this;
// 数组间元素拷贝
copyWithin(target: number, start: number, end?: number): this;
// 数组填值
fill(value: T, start?: number, end?: number): this
口说无凭,让我们来测试一下
忽略最后一次把===写成了=。。。顺便提一下,真是不太不清楚为什么要这样来设计函数。。
3. 支持负数逻辑的数组方法
从es5到es6+,感觉到了有越来越多的方法在支持传入负数来表示数组从后往前这种逻辑。例如arr.slice(-1)表示返回数组最后一个元素组成的数组。支持这种负数逻辑的方法一共有七个。es2015以后新增的方法有四个:includes、copyWithin、fill、at
// 数组浅拷贝
slice(start?: number, end?: number): T[]
// 数组添加、删除元素
splice(start: number, deleteCount: number, ...items: T[]): T[]
// 获取数组指定元素的下标
indexOf(searchElement: T, fromIndex?: number): number
// 判断是否包含指定元素
includes(searchElement: T, fromIndex?: number): boolean
// 数组间元素拷贝
copyWithin(target: number, start: number, end?: number): this;
// 数组填值
fill(value: T, start?: number, end?: number): this
// 获取数组指定索引值的元素
at(index: number): T
4. 可以实现数组拷贝的数组方法
这里指的是只调用一个方法就可以完成数组拷贝的方法,数组的拷贝都是指的是浅拷贝,如果数组元素是对象的话、那么修改拷贝的数组元素内容,原数组也会被影响。当然深、浅拷贝不是这里要谈论的点。目前可能完成调用一个方法就可以完成数组拷贝的方法只找到了两个:slice、concat
来看效果
5. 数组上的静态方法
在我的印象中,数组的静态方法常用的只有Array.from()来转换类数组。但其实静态方法一共有三个:
// 将可迭代的结构或类数组转为数组--常用的有set、map转为数组,arguments转为数组
from<T, U>(iterable: Iterable<T> | ArrayLike<T>, mapfn: (v: T, k: number) => U, thisArg?: any): U[]
// 将函数多个参数转为数组--相当于参数用[]包裹
of<T>(...items: T[]): T[];
// 判断一个值是否是数组(Object.prototype.toString.call(obj) === '[object Array]')
isArray(obj: any): boolean
静态函数的调用是直接通过类来调用,而不是通过实例,例如:
var a=1
Array.isArray(a) //false
6. 数组的构造函数
很抱歉,现在才提到了数组的构造函数。数组的构造函数其实有很强大的功能。
当向构造函数里传入一个参数的时候,表示创建一个长度为这个参数的数组,里面的元素都是empty(empty表示不是实际的元素)
当传入多个参数的时候,表示创建一个由这些参数组成的数组(同Array.of())
不过当参数只有一个的时候,我遇见过这样一个坑:
创建一个数组,里面的元素是1-100。
我当时是这样写的代码:
// 使用map遍历数组
var arr=new Array(100).map((_,index)=>index) //[empty × 100]
后面才发现上面的代码中map循环是没有执行的,需要改为下面的代码
// 创建出一个新的数组,再map遍历
var arr=[...new Array(100)].map((_,index)=>index)
第一次失败的原因,在于map遍历的时候不会遍历构造函数生成的空元素
来看一下mdn上面提供的map函数的ployfill
两张图结合起来看就会发现map循环使用in来排除了空元素的情况,而[...arr]会把[empty × 100]变为[undefined x 100],所以后面的遍历可以成功。
7. 数组的索引值说明
先看一下数组的索引值的类型申明
[index: number]: T;
这里表示索引值必须是数字类型,但是实际上数组也是可以使用字符串作为索引值来访问的。在mdn上,我曾看到过这么一句话:
years[2] 可以写成 years['2']。years[2] 中的 2 会被 JavaScript 解释器通过调用 toString 隐式转换成字符串
也就是说arr[n]和arr[n.toString()]的结果是一致的。
但是目前数组的索引值访问的缺陷在于没法使用负数来倒序访问,也就是说不管什么样的数组arr[-1]===undefined表达式是恒成立的。
在以前想访问数组的最后一个元素(访问而不修改原数组),常常用这两种 方法
// 使用length
arr[arr.length-1]
// 拷贝数组然后pop--数组长度过大时不推荐使用
[...arr].pop()
而现在官方推出了at方法,简化了上面的代码。
arr.at(-1)
8. 数组的链式操作
所有的函数方法里,只要函数调用之后的返回值是一个数组,那么这个函数就可以用于数组的链式操作里。链式操作会让代码更加整洁、思路更加清晰。
例如在一群学生集合里统计出男生的得分总和
var studentList = [
{ name: '小明', score: 15, sex: '男' },
{ name: '小明1', score: 21, sex: '男' },
{ name: '小明2', score: 25, sex: '男' },
{ name: '小明3', score: 22, sex: '男' },
{ name: '小明4', score: 26, sex: '男' },
{ name: '小明5', score: 30, sex: '男' },
{ name: '小花', score: 31, sex: '女' },
{ name: '小花1', score: 35, sex: '女' }
]
studentList
// 拿男生的数据
.filter(item => item.sex === '男')
// 求出总分
.reduce((acc, item) => acc + item.score, 0)
9. 所有的函数类型声明与示例
es6以后新增了很多实用的方法:map、flatMap、reduce等。在实际开发过程中,熟练应用数组的方法对简化代码发挥着至关重要的作用,不能一直都是for循环,是吧。
最后附上所有函数的类型声明与示例,有什么没补充到的欢迎沟通交流
interface ArrayCode<T> {
/**
* 返回数组最大索引加一的值
* 比如下面的代码
* var arr=[]
* arr[10]=1 // [empty*10,1]->此时的最大索引是10
* arr.length // 11
*/
length: number
/**
* 属性为数字类型,所以数组是不可以通过arr.1这种形式进行访问的,对象的键值不能以数字开头,且会被认为是字符串
*/
[index: number]: T;
/**
* 数组的构造函数。
* 如果只传入了一个参数,则表示创建一个长度为number的数组
* 如果创建了多个参数,则创建的数组为[...items]
* @param arrayLength |items
* @example
* var arr=new Array(10) => [empty*10]
* var arr=new Array(1,2,3) =>[1,2,3]
*/
constructor(arrayLength?: number): any[];
constructor(...items: any[]): any[];
/**
*
* @description 返回数组的字符串的表现形式。这就相当于调用数组的join(",")
* @return 数组的字符串的表现形式
* @example [1,2,3] ->1,2,3
* toLocaleString 和toString的返回值在我的测试环境下是一致的
*/
toString(): string;
/**
* @description 移除数组的最后一个元素,然后返回这个元素。如果数组是一个空数组的话,返回undefined,不会修改数组 ----count:1
* @return 数组的最后一个元素或者undefined
*
*/
pop(): T | undefined;
/**
* @description 在数组的末尾添加新的元素,返回添加之后的数组的长度 ----count:2
* @param items 这里的参数并不是数组,而是多个元素,传递的时候push(1,2,3),接收到的时候items为[1,2,3]
* @paramEmit 如果省略了该参数,多次执行不会有改变
*
* var arr=[1,2,3]
* arr.push() -->arr:[1,2,3]
* arr.push(undefined) -->arr:[1,2,3,undefined]
*
* @return 返回插入之后的数组长度
* @example
* var arr=[1,2,3]
* arr.push([4,5]) => arr:[1,2,3,[4,5]]
* arr.push(...[4,5]) =>arr:[1,2,3,4,5]
*/
push(...items: T[]): number;
/**
* @description
* 在数组的末尾添加items, 这里的items的每个元素可以是数组也可以是元素
* 但是如果是数组的话,只会遍历第一层,然后添加到原数组里
* @param items 可以是多个数组或者多个元素
* @paramEmit 不传参数,返回原数组的值。和slice方法一致
* @return 返回拼接好的新数组,不会修改之前任何的数组
* @example
* var arr=[1,2,3]
* arr.concat(1,2,[1,2])=> [1,2,3,1,2,1,2]
* var arr=[1,2,3]
* arr.concat([[1,2]],[1,2])=>[1,2,3,[1,2],1,2]
*/
concat(...items: ConcatArray<T>[]): T[]
concat(...items: (T | ConcatArray<T>)[]): T[]
/**
* @description 将所有的数组元素通过separator来拼接成一个字符串
* @param separator 这里如果省略的话,默认是逗号。省略就和数组的toString()逻辑相同
* @return 拼接好的字符串
* @example
* var arr=[1,2,3]
* arr.join() =>1,2,3
* arr.join("、") =>1、2、3
*/
join(separator?: string): string;
/**
* @description 反转数组元素的顺序. 这里不但会修改原数组,还会把修改后的元素返回。也就是说这个函数执行后,原数组和函数返回值的引用一样,目前看来是唯一一个具备这样特性的函数 count 3
* @return 反转后的数组
* @example
*
* var arr=[1,2,3]
* arr.reverse() =>[3,2,1]
* arr => [3,2,1]
*/
reverse(): T[];
/**
* @description 删除数组的第一个元素。如果是空数组,则返回undefined,不会改变原数组(空数组的前提)。count 4
* 逻辑是和pop类似的
* @return 删除的元素
*@example
* var arr=[]
* arr.shift() =>undefined
* var arr=[1,2,3]
* arr.shift() =>1;arr:[2,3]
*/
shift(): T | undefined;
/**
* @description 在数组的开头添加元素,返回添加之后的新数组的长度 count 5
* @param items 多个参数
* @paramEmit 如果省略则不会插入
* @return 新数组的长度
* @example
* var arr=[1,2,3]
* arr.unshift(0) =>4 ;arr:[0,1,2,3]
* arr.unshift() =>4
* arr.unshift(undefined) =>5
*/
unshift(...items: T[]): number;
/**
* @description 返回数组中位置为[start,end)的拷贝,两个参数都可以使用负数(负数会自动加上数组的length,-2表示倒数第二个数,-1表示倒数第一个数)
*
* 如果第一个参数是负数,那么会处理这个参数为Math.max(0, len + start)
* 如果第二个参数是负数,会给这个参数加上len
* 循环条件是 end-start>0
*
* @param start 默认是0
* @param end 默认是数组长度 而不是长度减一
* @return [start,end)的数组
* @example
*
* var arr=[1,2,3]
* arr.slice() => [1,2,3]
* arr.slice(0,1) =>[1]
* arr.slice(-1) =>[3]
* arr.slice(0,-1) =>[1,2]
*/
slice(start?: number, end?: number): T[];
/**
* @description 数组排序,方法将修改原数组并返回原数组的引用(和reverse的描述一致) --count 6
* @param compareFn 决定数组按照哪种方式排序,compareFn函数返回值小于0,前者小于后者,返回值等于0,相等,返回值大于0,前者大于后者
* @paramEmit 参数省略,将按照ASCII码从小到大的顺序
* @return 排序之后原数组的引用
* @example
* var arr=[5,4,1,2,3]
* arr.sort((a,b)=>a-b) =>[1,2,3,4,5]
* arr.sort((a,b)=>b-a) =>[5,4,3,2,1]
*
* var arr=['b','A','C','e']
* arr.sort() =>['A','C','b','e']
*/
sort(compareFn?: (a: T, b: T) => number): this;
/**
* @description 在数组的start位置起,删除deleteCount个元素,再在数组的start位置起,插入items个元素。会修改原数组 --count 7
* @param start 开始位置,支持负数的逻辑
* start如果小于0的话,那么start=Math.max(0,start+len)
* @param deleteCount 删除数量,具体删除的位置为[start,start+deleteCount)
* @paramEmit deleteCount 如果省略则表示从开始位置开始的元素全部删除
* @param items 增加的元素
* @return 只会返回被删除的元素
* @example
* var arr=[1,2,3,4,5,6]
* arr.splice(0) =>[1,2,3,4,5,6];arr:[]
* arr=[1,2,3,4,5,6]
* arr.splice(0,2,9,9) =>[1,2] ;arr:[9,9,3,4,5,6]
*/
splice(start: number, deleteCount: number, ...items: T[]): T[];
/**
* 从fromIndex位置开始,搜索searchElement第一次出现的位置,如果找不到,返回-1
* @param searchElement 查找的元素(判断相等是使用的===,所以NaN是没发查找的)
* @param fromIndex 查找的位置,支持负数
* fromIndex如果小于0的话,那么start=Math.max(0,start+len)
* @paramEmit fromIndex 如果参数省略,则从0开始查找
* @return 出现的位置
* @example
* var arr=[1,2,3,1]
* arr.indexOf(1) =>0
* arr.indexOf(0) =>-1
*
* var arr=[NaN]
* arr.indexOf(NaN) =>-1
*/
indexOf(searchElement: T, fromIndex?: number): number;
/**
* @description 判断元素在fromIndex之后是否存在 ,相当于arr.indexOf(searchElement, fromIndex)>-1,但不完全一样
* indexOf是不能处理NaN的,而includes可以识别 x === y || (typeof x === 'number' && typeof y === 'number' && isNaN(x) && isNaN(y));
* @param searchElement
* @param fromIndex 默认为0 支持负数逻辑
*/
includes(searchElement: T, fromIndex?: number): boolean;
/**
* 从fromIndex位置开始,搜索searchElement最后一次出现的位置,如果找不到,返回-1
* @param searchElement 查找的元素(判断相等是使用的===,所以NaN是没发查找的)
* @param fromIndex 查找的位置,支持负数
* @paramEmit fromIndex 如果参数省略,则从0开始查找
* @return 出现的位置
* @example
* var arr=[1,2,3,1]
* arr.lastIndexOf(1) =>3
* arr.lastIndexOf(0) =>-1
*
* var arr=[NaN]
* arr.lastIndexOf(NaN) =>-1
*/
lastIndexOf(searchElement: T, fromIndex?: number): number;
/**
* @description 判断数组中的所有元素是否都可以通过predicate函数,如果有一个不通过的话,循环立即结束,返回false。所有都通过,返回true
* 如果数组长度为零,那么直接返回true,即传入任意predicate函数,都通过
* @param predicate 判断函数,有三个参数。(当前项、当前序号、整个数组)
* @param thisArg 会给predicate函数绑定this指向,如果省略,则在严格模式下是undefined。因为箭头函数没法绑定this指向,所以如果predicate是箭头函数的话,thisArg绑定是不生效的
* @return 成功或者失败(true或者false)
* @example
* var arr=[1,2,3]
* arr.every(function(item){return item>this.test},{test:0}) =>true
* arr.every(function(item){return item<this.test},{test:0}) =>false
* arr.every(item=>item>this.test,{test:0}) =>false (1>NaN 和1<NaN都是不成立的(数值比较可以单独列一个章节))
*/
every<S extends T>(predicate: (value: T, index: number, array: T[]) => value is S, thisArg?: any): this is S[]
/**
* @description 判断数组中的是否存在元素可以通过predicate函数,如果存在一个通过的话,循环立即结束,返回true。所有都不通过,返回false
* 如果数组长度为零,那么直接返回false,即传入任意predicate函数,都不通过
* @param predicate 和every一致
* @param thisArg 和every一致
* @example
* var arr=[1,2,3]
* arr.some(item=>item>1) => true
*/
some(predicate: (value: T, index: number, array: T[]) => unknown, thisArg?: any): boolean;
/**
* @description 使用callbackfn函数遍历每一个元素
* @param callbackfn 同上
* @param thisArg
* @example
* var arr=[1,2,3]
* arr.forEach(item=>console.log(item))
* =>
* 1
* 2
* 3
*/
forEach(callbackfn: (value: T, index: number, array: T[]) => void, thisArg?: any): void;
/**
* @description 使用callbackfn函数遍历每一个元素、并把函数调用的结果存在新数组里,返回新数组的值
* @param callbackfn
* @param thisArg
* @example
* var arr=[1,2,3]
* arr.map(item=>item+1) =>[2,3,4]
*/
map<U>(callbackfn: (value: T, index: number, array: T[]) => U, thisArg?: any): U[];
/**
* @description 返回数组中满足predicate条件的元素
* @param predicate
* @param thisArg
* @example
* var arr=[1,2,3]
* arr.filter(item=>item>1) =>[2,3]
*/
filter<S extends T>(predicate: (value: T, index: number, array: T[]) => value is S, thisArg?: any): S[];
/**
* @description 循环数组中的所有元素,调用指定的回调函数,回调函数的返回值是累计的结果,并在下一次调用的时候作为previousValue传入
* @param callbackfn 回调函数,
* previousValue 第一个参数为上一次调用的返回值,若没有指定initialValue,则将数组的第一个元素设置为第一个参数
* currentValue 循环过程中的当前元素
* currentIndex 循环下标
* array 当前数组
* @param initialValue 第一次循环中的previousValue初始值
* @example
* var arr=[1,2,3]
* arr.reduce((acc,cur)=>acc+cur) =>6
*/
reduce(callbackfn: (previousValue: T, currentValue: T, currentIndex: number, array: T[]) => T, initialValue: T): T;
/**
* @description 和reduce相同,不同的点在于reduce的遍历顺序是从左往右,reduceRight的遍历顺序是从右往左
* @param callbackfn
* @param initialValue
*/
reduceRight<U>(callbackfn: (previousValue: U, currentValue: T, currentIndex: number, array: T[]) => U, initialValue: U): U;
/**
* 将[start,end)的元素拷贝到序号为[target,target+(end-start))处,返回原数组的引用 --count 8、
* 方法不会改变数组的长度,只是元素之间的拷贝
* @param target 如果负数,值为Math.max(0,target+len),负无穷大也就是取0
* @param start 超出范围(-len,len),循环不执行 ,省略为0
* @param end 超出范围(-len,len),循环不执行
* @example
* [1,2,3,4,5].copyWithin(-2) => [1,2,3,1,2]
* [1,2,3,4,5].copyWithin(-2,0,5) => [1,2,3,1,2]
*/
copyWithin(target: number, start: number, end?: number): this;
/**
* @description 返回一个新的Array Iterator对象
* 和对象的Object.entries()方法不同:Object.entries不会遍历没有对应元素的索引值,而数组的 entries会返回没有元素的索引值[n,undefined]
* 迭代器对象可以使用迭代器.next()和done进行遍历,也可以使用for of进行遍历,但是不能使用forEach进行遍历
* @example
* 正常使用
* var arr=['x','y','z']
* var iterator=arr.entries()
* for (let e of iterator) {
* console.log(e);
* }
* =>
* [0, "x"]
* [1, "y"]
* [2, "z"]
* Object.entries和数组的entries比较
* var arr=[1,,2]
* Object.entries(arr) =>[["0",1],["2",2]]
* var iterator=arr.entries()
* for (let e of iterator) {
* console.log(e);
* }
* =>
* [0, 1]
* [1, undefined]
* [2, 2]
*/
entries(): IterableIterator<[number, T]>;
/**
* @description 返回一个包含数组每个索引值的可迭代对象
* @example
* var arr=['x','y','z']
* var iterator=arr.entries()
* for (let e of iterator) {
* console.log(e);
* }
* =>
* 0
* 1
* 2
*/
keys(): IterableIterator<number>;
/**
* @description 返回一个包含数组每个元素的可迭代对象
* 不知道这几个方法的实际用途是什么
* @example
* var arr=['x','y','z']
* var iterator=arr.entries()
* for (let e of iterator) {
* console.log(e);
* }
* =>
* 'x'
* 'y'
* 'z'
*/
values(): IterableIterator<T>;
/**
* @description 数组的[start,end)范围内的元素填充为value ,修改原数组、并返回原数组的引用
* @param value
* @param start 支持负数逻辑
* @param end 支持负数逻辑
* @example
* var arr=new Array(3)
* arr.fill('x') =》['x','x','x']
*/
fill(value: T, start?: number, end?: number): this
/**
* @description 返回数组中满足提供的函数参数的第一个元素的值。否则返回 undefined indexOf的逻辑的加强版,但是不支持指定fromIndex参数
* @param predicate 和map一致
* @param thisArg
* @example
* var stu={name:"张三"}
* var arr=["张三","李四","王五"]
* arr.find(function(item){return item===this.name},stu) //注意箭头函数不能修改this指向,所以这里如果同时使用箭头函数和thisArg,thisArg绑定不生效
*/
find<S extends T>(predicate: (this: void, value: T, index: number, obj: T[]) => value is S, thisArg?: any): S | undefined
/**
* @description 同find函数、找到返回下标值,没找到返回-1
* @param predicate
* @param thisArg
*/
findIndex(predicate: (value: T, index: number, obj: T[]) => unknown, thisArg?: any): number
/**
* @description 把depth+1维数组转为一维数组,depth为数组维度
* 不影响原数组
* 一行代码模拟这个函数
* const flat = (arr, depth=1) => depth > 0 ? arr.reduce((acc, cur) => acc.concat(Array.isArray(cur) ? flat(cur, depth - 1) : cur), []) : arr.slice()
* @param depth
* @example
* var arr=[1,2,[3,4,[5,6]]]
* arr.flat(1) => [1,2,3,4,[5,6]]
*/
flat(depth?: number): T[]
/**
* @description flatMap与map的类型声明一摸一样,但是功能很强大,可以实现fliter、map、map+filter、reduce+concat的功能
* 这个函数相较于map的优势在于 map函数循环之后的结果个数和之前是一摸一样的,所以有的时候需要使用map+filter来组合使用。但是flatMap可以自定义返回元素的个数
* @param callbackfn
* @param thisArg
* @example
* 实现过滤
* var arr=[1,2,3,4,5]
* arr.flatMap(item=>item>3?[item]:[]) => [4,5]
* 实现map
* var arr=[1,2,3,4,5]
* arr.flatMap(item=>[item+1]) =>[2,3,4,5,6]
* 实现奇数拆成偶数+1的效果
* var arr=[1,2,3,4,5]
* arr.flatMap(item=>item%2===0?[item]:[item-1,1]) =>[0, 1, 2, 2, 1, 4, 4, 1]
*/
flatMap<U>(callbackfn: (value: T, index: number, array: T[]) => U, thisArg?: any): U[];
/**
* @description 注意这个是静态方法,而不是原型方法。将有可以迭代的元素或者类数组转为数组,如果有剩下两个参数,再执行map函数
* @param iterable
* @param mapfn
* @param thisArg
* @example
* map结构转为数组,类似于Object.values({'name":"小明"}) =>["小明']
* var map=new Map()
* map.set('name","小明')
* Array.from(map,(item)=>item[1]) =>['小明']
* set结构转为数组
* var set=new Set([1,2,3])
* Array.from(set) =>[1,2,3]
*/
from<T, U>(iterable: Iterable<T> | ArrayLike<T>, mapfn: (v: T, k: number) => U, thisArg?: any): U[]
/**
* 将参数转为数组元素
* @param items
* @description
* var arr=Array.of(1,2,3) =>arr=[1,2,3]
*/
of<T>(...items: T[]): T[];
/**
* @description 判断一个值是否是数组(Object.prototype.toString.call(obj) === '[object Array]')
* 此方法是一个静态方法
* @param obj
*/
isArray(obj: any): boolean
// 还没有纳入正式标准的函数
/**
* @description 通过index获取数组的元素,支持负数
* @param index
* 【-len,len)之内是取数组的值,超出范围返回undefined。这里的负数逻辑和之前是不一样的,超出范围返回undefined
* @paramEmit 如果index省略的话,默认值是0
*/
at(index: number): T;
}