前言
什么的group?我咋没听说数组有这个方法?抱歉,MDN文档也没有。
消息来源于大淘宝前端技术的一则推文,新增的[].group已经提上日程了。
github围观地址:github.com/tc39/propos…
你将收获
- 加深对this和原型链的理解
group的实现groupToMap的实现- 数组方法手写模板
邂逅
Array.prototype.group
我的理解:group()方法创建一个新对象,其包含的key由所提供的函数指定的分组依据,value对应通过函数分组测试的所有元素。
groupToMap(),用法相似,但key可以是对象类型。
听不懂没关系,直接看官方的例子+语法:
语法
Array.prototype.group(`callbackfn`[,`thisArg`])
callbackfn应该是一个接受三个参数的函数。Group 按升序对数组中的每个元素调用callbackfn一次,并构造一个新的数组对象。callbackfn返回的每个值都被强制为属性键,并且关联的元素根据此属性键包含在构造对象的数组中。- 如果提供了
thisArg参数,它将作为callbackfn的每次调用的this值。如果未提供,则改为使用未定义的。 - 使用三个参数调用
callbackfn:元素的值、元素的索引和被遍历的对象。 - Group不会直接对调用它的对象产生影响,但对
callbackfn的调用可能会对该对象产生影响。 - 在第一次调用
callbackfn之前设置 group 处理的元素的范围。callbackfn不会访问在对组的调用开始后附加到数组的元素。如果数组中现有的元素在传递给callbackfn时改变了它们的值,那么当组访问它们的时候它们的值就是这个值; 在组调用开始之后和被访问之前删除的元素仍然被访问,并且要么从原型中查找,要么没有定义。 - Group的返回值是一个不继承
Object的对象。
Array.prototype.group(`callbackfn`[,`thisArg`])
- 同上,但GroupToMap的返回值是一个Map。
实现
先上代码
Array.prototype.group = function (callback, thisArg = null) {
// 参数合法性判断
if (typeof callback !== "function") {
throw new TypeError(`${callback} is not a function`);
}
const arr = this;
const length = this.length;
const grouper = Object.create(null);
for (let i = 0; i < length; i++){
const key = callback.call(thisArg, arr[i], i, arr)
if (!grouper[key]){
grouper[key] = [ arr[i] ]
}else {
grouper[key].push(arr[i])
}
}
return grouper;
};
// 测试
const array = [1, 2, 3, 4, 5]
const res = array.group((num, index, array) => {
return num % 2 === 0 ? 'even': 'odd'
})
console.log(res);// { odd: [ 1, 3, 5 ], even: [ 2, 4 ] }
Array.prototype.groupToMap = function (callback, thisArg = null) {
// 参数合法性判断
if (typeof callback !== "function") {
throw new TypeError(`${callback} is not a function`);
}
const arr = this;
const length = this.length;
const grouper = new Map();
for (let i = 0; i < length; i++){
const key = callback.call(thisArg, arr[i], i, arr)
const groupedList = grouper.get(key);
if (!groupedList){
grouper.set(key,[ arr[i] ])
}else {
groupedList.push(arr[i])
}
}
return grouper;
};
const odd = { odd: true };
const even = { even: true };
const array = [1, 2, 3, 4, 5]
const map = array.groupToMap((num, index, array) => {
return num % 2 === 0 ? even: odd;
});
console.log(map);// Map(2) { { odd: true } => [ 1, 3, 5 ], { even: true } => [ 2, 4 ] }
逐步分解
第1/2点考察了this和原型链,有的人在简历上写熟悉this和原型链,挑一个数组方法让其实现却不知从何下手,并不是真的深入理解。
[].group可知,让每个数组对象都能使用该方法,可将其挂在数组原型对象上Array.prototype.group = function(){}- 函数内如何获取调用group方法的数组,答案是通过this,这里涉及到this的指向,
[].group()是隐式绑定,this指向该对象 - 参数处理:第一个参数
callback必填,否则抛出错误,第二个参数thisArg可不传,我们给默认值null - group返回值是对象,对象每个key是分组条件,value对应符合的元素
{
key1:[],
key2:[],
...
}
但官方语法提到,该对象不继承自Object,我们通过
Object.create(null)来创建一个没有原型对象为null的对象 - group会进行一次数组的遍历,我们采用最朴素的for循环
- 在第一次调用
callback时,需要group处理的元素的范围就已经确定了,此处我们记住数组的长度,作为for循环的循环次数;故callback的执行期间往后追加元素并不会被处理,插入到范围内的则会 - 每次循环,执行callback并绑定this,可用
call/apply callback的执行结果为分组的条件,如果grouper对象没有对应的key则创建数组并加入当前元素,否则就push追加到数组中;分组的多少,取决于callback返回值的种类- 最后返回grouper对象
- groupToMap方法只需在group基础上,将容器换成Map(其key不局限于字符串,可以是对象),对map的使用
set/get进行存取数据
最后,以上并非最佳实现,如有不足或错误,欢迎斧正~
通用模板
基于以上代码,可以抽取重复的片段,作为每次手写的一个框架,再在这个框架上去实现某个数组方法
Array.prototype.myXXX = function (callback, thisArg = null) {
// 参数合法性判断
if (typeof callback !== "function") {
throw new TypeError(`${callback} is not a function`);
}
const arr = this;
const length = this.length;
// 有返回值的可以在此放一个默认值
for (let i = 0; i < length; i++){
const res = callback.call(thisArg, arr[i], i, arr)
// 需要callback的执行结果,或对结果进行一个处理
}
// return
};
总结与思考
留给大家的思考题(才疏学浅不知为何)
- 为什么group/groupToMap合并为一个API,显然前者实现是后者的一个特例
- Group的返回值为什么需要是一个不继承
Object的对象。