文章目录
一、前言
JavaScript
中,有一些被异常低估的技巧,恰恰我认为这些技巧对于前端朋友们拉高自己代码层次是非常有帮助的,它们分别是FlatMap
、数组方法链的顺序、充分使用reduce
、充分使用generator
、充分使用原生JS
类。
二、FlatMap
FlatMap
本质上将 map
和 filter
数组方法的技术结合在一起(非语法糖)。强烈建议使用 flatMap()
而不是 filter()
和 map()
的组合。
FlatMap
只需一次遍历,不会产生中间数组,但filter()
和map()
组合会产生中间数组。
2.1、使用 filter
和 map
接下来我们看一个具体例子:
console.time("filterAndMap")
const numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
const squaredOddNumbers = numbers.filter(num => num % 2 !== 0).map(num => num * num)
console.log(squaredOddNumbers); // [1, 9, 25, 49, 81]
console.timeEnd("filterAndMap")
我们能看到使用 filter + map
的方式使用了 0.31ms
2.2、使用 filterMap
接下来再看看使用 filterMap
的性能:
console.time("filterAndMap")
const numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
const squaredOddNumbers = numbers.flatMap(num =>
num % 2 !== 0 ? [num * num] : []
);
console.log(squaredOddNumbers); // [1, 9, 25, 49, 81]
console.timeEnd("filterAndMap")
使用 filterMap
处理同样的情况只需要 0.22ms
三、数组方法链的排序
JavaScript
中一些常见且广受欢迎的数组方法包括 .filter()
、.find()
、.map()
以及 .reduce()
。这些方法可以灵活组合,从而创造出一些非常优秀的模式。
// 一个数组方法,仅对奇数进行排序并将其立方
numbers
.sort((a, b) => a - b)
.filter((n) => n % 2 !== 0)
.map((n) => n ** 3);
乍一看,你可能会觉得这段代码写得不错,但你有没有发现代码是先对数字排序,接着再进行筛选。事实上,应该先进行筛选,再进行排序和提高幂次,就能减少一部分没必要的遍历。这样一来,我们就有机会优化由.
链接的数组方法。
下面是优化后的代码:
const numbers = [9, 3, 6, 4, 8, 1, 2, 5, 7];
// 一个数组方法,仅对奇数进行排序并将其立方
numbers
.filter((n) => n % 2 !== 0)
.sort((a, b) => a - b)
.map((n) => n ** 3);
四、充分使用reduce
让我们来看一个例子,这个例子是关于计算数组中每个元素出现的次数。通常,大多数人可能会选择使用 forEach
或者 for
循环来完成这个任务,但使用 reduce
可以使代码更简洁。
假设我们有一个数组,数组元素如下:
let arr = ['apple', 'banana', 'apple', 'orange', 'banana', 'banana'];
如果我们使用 forEach
来计算每个元素出现的次数,代码可能会是这样的:
let count = {};
arr.forEach(function(i) { count[i] = (count[i]||0) + 1;});
上述代码虽然能够完成任务,但是如果我们使用 reduce
,代码会更加简洁且易读:
let count = arr.reduce((prev, cur) => {
prev[cur] = (prev[cur] || 0) + 1;
return prev;
}, {});
在这个例子中,reduce
函数接受两个参数,一个是累加器(在这里是一个对象),另一个是当前的元素。我们检查累加器中是否已经有了当前元素作为键,如果有,就将其值加一,如果没有,就设置其值为一。最后,返回累加器对象,它将包含每个元素及其出现的次数。
这个例子清楚地展示了 reduce
的优势,它可以使代码更加简洁且易读,并且提供了一种优雅的方式来处理这种常见的问题。
五、充分使用Generators
生成器的使用场景之一就是处理大量数据的情况,比如在实现无限加载的功能时。让我们通过一个具体的例子来看看如何使用生成器来实现这个功能,并比较其与其他方法的优劣。
首先,我们定义一个异步生成器函数 fetchData
,该函数会不断地从某个 API
获取数据。
asyncfunction* fetchData() {
let pageIndex = 1;
while (true) {
const url = `https://fakeapi.com/data?page=${pageIndex}`;
const res = await fetch(url);
const data = await res.json();
yield data;
pageIndex++;
}
}
然后,我们可以在需要的时候从这个生成器获取数据。
asyncfunction main() {
const itr = fetchData();
// 根据用户交互或其他条件来决定何时获取数据
console.log(await itr.next());
}
这种方法的优点在于,我们可以根据需要控制数据的获取,而不是一次性获取所有数据。这样可以有效减少内存的使用,并提高程序的响应速度。
相比之下,如果我们使用传统的方法(比如回调或 Promise
)来获取数据,那么在数据量大的情况下,我们可能需要一次性获取所有数据,或者使用复杂的逻辑来控制数据的获取。这不仅会增加代码的复杂性,而且可能会导致内存溢出或程序响应缓慢。
因此,对于处理大量数据的场景,使用生成器和迭代器是一种更好的选择。
六、充分使用原生JavaScript
类
Javascript
内置了一些原生的javascript
类,可以帮助我们轻松创建/实例化URL
、Headers
等对象。首先我们看看你是怎么构建URL
参数的:
asyncfunction getUrl(userId, limit, category){
return`https://fakestoreapi.com/products${category ? `/category/${category}` : ""}${limit ? Number(limit):""}${userId? Number(userId):""}`;
}
上面的代码很混乱,很可能会出错,并且每次添加其他参数时都需要在最后添加一些规则。通过使用原生类如URL
,我们可以改进我们的代码。
改进后的代码如下:
function constructURL(category, limit, userId) {
const baseURL = "https://fakestoreapi.com/products";
const url = new URL(baseURL);
const params = new URLSearchParams();
if (category) url.pathname += `/category/${category}`;
if (limit) params.append('limit', Number(limit).toString());
if (userId) params.append('userId', Number(userId).toString());
url.search = params.toString();
return url.toString();
}
这样,就可以在同一个函数中处理复杂的URL
条件。其中,这里的内置URL对象遵循了 BuilderPattern
,它是 JavaScript
许多设计模式
的其中之一,可以将复杂逻辑隐藏在一个单独的地方,从而提高可读性。
七、最后
本人每篇文章都是一字一句码出来,希望对大家有所帮助,多提提意见。顺手来个三连击,点赞👍收藏💖关注✨,一起加油☕