依托能够运行的答辩 和 不能执行但是非常优雅的代码 你选择哪一个

130 阅读3分钟

开启掘金成长之旅!这是我参与「掘金日新计划 · 2 月更文挑战」的第 11 天、第 11 篇,点击查看活动详情

前言

今天在开发的时候遇到一个需求是:

给你一个Array<object>,数组内的对象有一个共同的属性 value ,形如:

let target = [{label: 'obj1',value: '001'}, {label: 'obj2',value: '002'},{label: 'obj3',value: '001'}]

其中如果不同的对象 value 执相同被视作重复,简单来说就是对属性value去重,重复的条目保留任意一个。我们来探索一下该如何实现

简单朴素

这个需求其实很简单,我们用最简单的for循环来这这实现一下:

let target = [{label: 'obj1',value: '001'}, {label: 'obj2',value: '002'},{label: 'obj3',value: '001'}];
​
let valueMap = {};
for(let i = 0 ; i < target.length ; i++){
    valueMap[target[i].value] = target[i];
}
​
let result = Object.values(valueMap)

这里用一个对象去记录数组内每个对象内 value 属性对应的对象,当遇到重复的 value 时,后来的对象自然会顶替掉原来的值,再用 Object.values 方法将对象展开,最终得到去重后的结果。

但是这里用对象会有些问题,就是 value 的值可能并不是一个简单的字符串,可能是数字、对象,甚至混合。

比如当两个 value 值一个是数字 1 ,而另一个是字符串 '1' 时,他们会被对象视作为同一个键值:

let obj = {};
obj[1] = 'value1';
obj['1'] = 'value2';
​
console.log(obj);//{ 1 : 'value2' }

其实是因为对象只能用 string 和 symbol 类型作为键值,这里会被类型转换为字符串,因此被视为同一个键值,所以我们这里改成Map(),对Map的具体介绍请期待下一篇文章,简单来说就是 Map 类型可以任意类型作为键值,并且读写性能要优于 object。

let target = [{label: 'obj1',value: '001'}, {label: 'obj2',value: '002'},{label: 'obj3',value: '001'}];
​
let valueMap = new Map();
for(let i = 0 ; i < target.length ; i++){
    valueMap.set(target[i].value, target[i])
}
​
let result = valueMap.values()

我们今天的主角并不是 Map ,我们这里改用了用最简单的 for 循环给 valueMap 赋值去重,我们今天的主角是 reduce ,我们尝试用 reduce 来改写一下

reduce()

reduce是Array原型上的一个高级方法,MDN中是这样介绍他的

reduce() 方法对数组中的每个元素按序执行一个由您提供的 reducer 函数,每一次运行 reducer 会将先前元素的计算结果作为参数传入,最后将其结果汇总为单个返回值。

第一次执行回调函数时,不存在“上一次的计算结果”。如果需要回调函数从数组索引为 0 的元素开始执行,则需要传递初始值。否则,数组索引为 0 的元素将被作为初始值 initialValue,迭代器将从第二个元素开始执行(索引为 1 而不是 0)。

reduce()有两个参数,第一个是一个回调函数,第二个参数是迭代的初始值,不传第二个参数迭代的初始值就是数组的第一个值,回调函数的有4个入参分别是:

  • previousValue(前一次调用 callbackfn 得到的返回值)
  • currentValue(数组中正在处理的元素)
  • currentIndex(数组中正在处理的元素的索引)
  • 被遍历的对象

更详细的内容可以前往MDN官网查看,以下是一个简单的示例

let sum = [0, 1, 2, 3].reduce(function (previousValue, currentValue) {
  return previousValue + currentValue
}, 0)
// sum is 6

那么我们怎么用它来改造我们上面的代码呢,废话不多说

let target = [{label: 'obj1',value: '001'}, {label: 'obj2',value: '002'},{label: 'obj3',value: '001'}];
​
let valueMap = target.reduce((acc, cur) => {
    valueMap.set(cur.value, cur);
},new Map())
​
let result = Object.values(valueMap)

这里以一个新的 Map 对象作为迭代器的初始值,每次迭代就和上面 for 循环中做的事一样了。

总结

实际上因为每次迭代都需要执行一次函数,虽然同样是一次循环它的性能是略(略略略略)低于 for 循环的。但是显而易见的代码更加简洁,优美,精确。虽然并不是说这样的代码就一定是好的,最适合的才是最好的,同样的实现效果,在允许的情况下我们自然要去追求所谓更加高大上的(就是为了装X)。留给大家一个问题:依托能够运行的答辩不能执行但是非常优雅的代码,你选择哪一个?某音老一人昨天发视频选择了不能执行但是非常优雅的代码,他认为让不能执行但是非常优雅的代码运行起来,远比让依托答辩变得优美简单得多,你的看法呢?最后再打个广告,关注公众号程序猿青空,不定期分享各种优秀文章、学习资源、学习课程,能在未来(因为现在还没啥东西)享受更多福利。