翻译 | 杨小爱
JavaScript 灵活而强大。但是,有时可能会很棘手。在 JavaScript 不断演进并带来新挑战的同时,我们在日常工作中也反复遇到类似的问题。
在本文中,我们将分享6个可以帮助您编写干净且可维护的 JavaScript 代码的小技巧。
现在,我们就开始吧。
1、同时或顺序执行多个异步Promise
JavaScript 默认是同步的。为了处理异步代码,一种常见的方法是使用 Promise。与可能导致回调地狱的回调相比,Promise 提供了一种更好的方法来处理多个异步请求。
同时处理多个承诺
JavaScript 提供了 Promise.all() 方法来处理并发请求。
// simulate async operation
function fetchMockData(name, timeToWait = 2000) {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve({ name: name });
}, timeToWait);
});
}
const allPromises = [fetchMockData('John'), fetchMockData('Peter')];
Promise.all(allPromises)
.then((results) => {
const [first, second] = results;
console.log(first, second);
})
.catch((err) => {
console.log(err);
});
顺序处理多个承诺
当您需要一个接一个地执行多个异步请求时,可能会有点棘手。您的第一直觉可能是使用 forEach 或 map,但它们没有按预期工作。承诺不会等到它完成才开始下一个。
不使用第 3 方库,最好的方法是使用 reduce 方法。
const allPromises = [fetchMockData('John', 4000), fetchMockData('Peter')];
allPromises.reduce(async (p, curr) => {
await p;
return curr.then((result) => {
console.log('result:', result);
return curr;
});
}, Promise.resolve());
在上面的reduce 方法中,我们返回一个promise,它在每次迭代中解析为另一个promise。结果是一系列承诺,使异步操作一个接一个地执行。
如输出所示,虽然,我将第一个 Promise 的超时设置为 4 秒,第二个 Promise 的默认值为 2 秒,但第一个 Promise 在第二个之前先解决。
尽管reduce 本身是同步的,但它允许我们将promise 返回给累加器,这样可以使解决方案运行良好。
2、使用 console.time 解决性能问题
有时,我们需要调试 JavaScript 函数来分析性能。开箱即用的 console.time 方法可以帮助我们测量执行时间。
控制台对象提供 time() 和 timeEnd() 方法。
首先,我们使用唯一的字符串标签调用 console.time() 方法,它启动一个计时器来跟踪代码执行的持续时间。
然后,我们运行要测量的函数。
最后,我们使用相同的标签调用 console.timeEnd() ,持续时间将在浏览器控制台中打印出来。
如果需要调试成多步骤的代码,可以启动多个定时器,用单独的定时器测量不同的步骤,以获得更清晰的图景。
这是一个带有两个计时器的示例:
function accumlateNumbers() {
let output = 0;
for (var i = 1; i <= 4000000; i++) {
output += i;
}
return output;
}
function callAccumlateFunction() {
const timeLabel = 'Time taken accumlateNumbers';
console.time(timeLabel);
const output = accumlateNumbers();
console.timeEnd(timeLabel);
}
const timeLabel2 = 'Time taken by callAccumlateFunction';
console.time(timeLabel2);
console.log(callAccumlateFunction());
console.timeEnd(timeLabel2);
输出是:
Time taken accumlateNumbers: 9.656005859375 ms
Time taken by callAccumlateFunction: 10.19677734375 ms
请注意,console.time 不适合需要高精度的时间测量。
3、 使用选项对象模式来处理传递给函数的多个参数
options 对象模式是为了解决向函数传递多个参数的问题。
使用将参数列表传递给函数的正常方式,我们需要注意参数的顺序。不正确的顺序会造成难以检测的缺陷。
function createUser(lastName, firstName, jobTitle, role){};
// we try to create a admin user
createUser(“John”,”Paul”,"admin", ”Manager”);
使用选项对象模式,我们只需要传递一个参数,它是一个包含所有参数选项的命名键的对象。
function createUser({lastName, firstName, jobTitle, role}){};
const user = {
firstName: 'John',
lastName: 'John',
jobTitle:'Manager',
role: 'Admin'
};
createUser(user);
如上面的代码片段所示,不仅我们不需要担心参数的顺序,而且带有选项对象模式的命名参数使代码更易于阅读。
选项对象模式通常用于四个或更多参数的情况。
4、组合多个函数
函数组合是将多个函数组合在一起,并将每个函数应用于前一个函数的结果的方法。在正确的用例中使用时,函数组合可以使您的代码简洁优雅。
这是一个简单的例子:
const applyFixDiscount= (x) => x - 20
const applyVipOffer = (x) => x / 2
const getDiscountedPrice = (x) => applyVipOffer(applyFixDiscount(x))
console.log(getDiscountedPrice(100)) // 40
上述方法有效,但当更多功能组合在一起时将难以阅读。更好的方法是使用下面的 compose 函数。
compose =
(...fns) =>
(initialVal) =>
fns.reduceRight((val, fn) => fn(val), initialVal);
const getDiscountedPrice = compose(applyVipOffer, applyFixDiscount);
console.log('price:', getDiscountedPrice2(100)) // 40
通用的 compose 函数可以将多个函数作为输入并一一调用。因此我们称 compose 为高阶函数。高阶函数的优势在于它能够以非常有表现力的方式组合多个操作。
请注意,它使用了 reduceRight,这意味着函数是从右到左执行的。另一种方法是下面的管道方法。它使用reduce,所以顺序是从左到右。
pipe = (...fns) => (initialVal) => fns.reduce((val, fn) => fn(val), initialVal);
const getDiscountedPrice = pipe(applyFixDiscount, applyVipOffer);
应用函数组合鼓励开发人员将程序分解为更小的部分,并将动作或行为抽象为函数。它让你首先考虑输入和输出,而不是专注于实现细节。
结果将是更具可读性、可测试性和可重用性的代码。
在实际项目中,函数组合的正确用例包括数据处理、复杂规则计算、工作流操作等。
5、使用解构来提取数据
解构是一种将值从对象属性或数组解包到多个变量的简单而简洁的方法。
解构的基本例子是:
const user = {
name: 'John Paul',
age: 23
};
// from object properties
const {name, age} = user;
const count= ['one', 'two', 'three'];
// array destructuring
const [first, second, third] = count;
提供了一些有用的破坏功能,包括默认值、跳过数组元素、分配新变量名等。您可以在此处找到完整列表。
下面是一些我经常使用的实际例子。
从函数结果中析构
function getUser() {
return {name: ‘John’, age: 24};
}
const {name, age} = getUser(); // name='John', age=24
拆分数组
const [first, ...rest] = ['1', '2', '3', '4'];
// output: first='1', rest=['2', '3', '4']
获取数组的第一个元素
const fruits = [‘apple’, ‘orange’, ‘pear’];
[first] = fruits; // first= 'apple'
销毁 promise.all() 的结果
Promise.all([ promise1, promise2, promise3])
.then( results =>{
const [first, second, third] = results;
})
6、有效地使用数组
数组是我们大多数人每天处理的最常见的数据结构。以下是对数组操作的一些提示:
使用slice不变地对数组进行排序
我们经常想对一个数组进行排序并得到一个不可变的副本。不幸的是, .sort 会改变原始数组。使用下面的slice,我们可以在不影响原始数组的情况下获得一个排序数组。
const newArr = arr.slice().sort()
请注意,slice从原始数组中返回元素的浅拷贝。如果您需要进行深度克隆,您可能喜欢使用不同的方法。
从数组中删除重复项
有多种方法可以从数组中删除重复项。最简单也是我最喜欢的方法是使用 Set。
Set 是在 ES6 中引入的,它表示一个唯一值列表。在下面的示例中,我们使用扩展运算符将 Set 操作的结果作为数组返回。
const arr = [1,2,3,2,3,4,5];
console.log([...new Set(arr)]); // [1,2,3,4,5]
请注意 Set 方法仅适用于原始值。
从数组中过滤掉虚假值
在 JavaScript 中,假值可以是空字符串、false、0、null、NaN 或 undefined。下面是我最喜欢的从数组中过滤掉虚假值的方法。
const arrToFilter = ["user", "", 0, NaN, 9, true, undefined, "red", false];
const result = mixedArr.filter(Boolean);
console.log(result); // returns ["user", 9, true, "red"]
如果您之前没有使用过它,您可能想知道 filter(Boolean) 是如何工作的?
Boolean 是一个对象包装器。在 filter(Boolean) 方法中,数组中的每一项都被传入并评估如下。结果为真或假,假值将被过滤掉。
.filter(x=> Boolean(x));
使用 Array.every 和 Array.some 来简化代码
Array.every 和 Array.some 是简化代码的非常方便的方法。与其他方法如 forEach 或 reduce 相比,Array.every 和 Array.some 使代码更具可读性和简洁性。
const users = [
{ name: 'john', role: 'admin' },
{ name: 'peter', role: 'dev' },
{ name: 'mary', role: 'dev' }
];
const isAllDeveloperRole = users.every(f => f.role === 'dev');
const hasDeveloperRole = users.some(f => f.role === 'dev');