太多关于 JavaScript
技巧的文章只涵盖了数组函数的基础知识或对代码的明显改进。本文将更加深入,帮助改进每天编写的代码。
有时,我们想等待某件事发生。虽然这个任务可能会变得复杂(例如,使用非阻塞循环),但对于大多数等待问题有一个简单的解决方案:Promise
。
可以在给定的超时后解决:
new Promise((resolve) => {
setTimeout(() => {
// 做一些事情
resolve();
}, 1000);
});
这个promise
将在大约 1 秒后解决。还可以将其存储在变量中并使用await
阻塞一秒钟(注意潜在的用户体验问题)。虽然我们早在日常开发中很容易找到上面代码片段的用例,但它意味着一个更有用的技巧。
可以使用 Promise
作为信号量:有时,我们想要执行异步、长时间运行的流程。但用户可以一次又一次地触发此过程。因此,我们需要确保正在运行的进程必须先完成,然后用户才能再次启动它。就是这样:
let processStatus = null;
function myProcess() {
if (processStatus) {
return;
}
processStatus = new Promise(resolve => {
// 长任务
setTimeout(() => {
resolve();
}, 5000);
})
.then(() => {
processStatus = null;
});
}
用户只能在没有活动进程时单击此按钮。这有助于避免多次获取相同的数据。
我们可能经常通过forEach
方法来处理数组,但是它还有一个特别强大的能力:异步循环。
const asyncArr = [
new Promise(resolve => setTimeout(resolve.bind(this, 1), 2000)),
new Promise(resolve => setTimeout(resolve.bind(this, 2), 500)),
new Promise(resolve => setTimeout(resolve.bind(this, 3), 5000)),
new Promise(resolve => setTimeout(resolve.bind(this, 4), 1000)),
];
asyncArr.forEach(async (el) => {
const i = await el;
console.log(i);
});
// 2,4,1,3
虽然使用 for-of
循环也可以做到这一点,但使用 await
读起来更加优雅。这是使用 for-of
循环的相同示例。
for (const el of asyncArr) {
el.then(console.log);
}
不要让 for-of 循环更简洁这一事实欺骗了您。在此示例中,我们不使用 进行任何计算i。then然而,想象一下在for-const 循环使用的函数体中进行更多计算。
虽然示例中的for...of
看起来更简洁,是因为没有对i
进行更复杂的运算,但是我们想象一下如果在then
方法里做更多的运算是不是更复杂一点。
使用forEach
可能会有两个问题:
首先,它没有返回值。这意味着我们可能要更改原始数组,或者根本不更改。如果决定修改数组,则会产生副作用,如果我们根本不更改它,这些副作用可能很难调试。
第二,不稳定。如果不同步循环所有元素,我们无法按照原始数组的顺序记录结果。
为了避免这种情况,我们可以Promise.all
与map
, 这将产生一个新数组,其中异步调用接收到的值的顺序与原始数组相同。
const asyncArr = [
new Promise(resolve => setTimeout(resolve.bind(this, 1), 2000)),
new Promise(resolve => setTimeout(resolve.bind(this, 2), 500)),
new Promise(resolve => setTimeout(resolve.bind(this, 3), 5000)),
new Promise(resolve => setTimeout(resolve.bind(this, 4), 1000)),
]
Promise.all(asyncArr)
.then(console.log);
// [1,2,3,4]
这个技巧简单但功能强大。在很多情况下,物品们会发现自己编写了 else
块,而只需 2 秒钟的思考就可以避免这种情况。
利用 return
语句可以帮助我们消除第一组不必要的 else
块。请参阅下面的示例:
function myFun() {
if (x > 10) {
// do something
} else {
// do something else
}
return someVar;
}
这可以重构为
function myFun() {
if (x > 10) {
// do something
return someVar;
}
// do something else
return someVar;
}
大家可能会反对:这些例子的大小相差不大——何必呢?这与大小无关,而是与可读性和降低复杂性有关。
任何 if
和 else
语句都会增加函数的复杂性。当遇到else
块时,它有多长?if
块有多长?
这里有一条经验法则:使用 if
语句处理错误并尽快返回。然后,该函数应该在任何 if/else
之外执行它应该执行的操作。
大家可能会有疑问:我什么时候知道我违反了单一责任原则?我什么时候意识到我的函数不止做一件事?if-else
可以作为一个指标!
function myFun() {
if (x > 10) {
// do something
} else {
// do something else
}
if (y < 100) {
// do something
} else {
// do something else
}
return someVar;
}
特别是当一个函数中有多个 if-else
块时,很可能违反了该函数的 SRP
。上面的例子可以重构为
function myFunc() {
const xValid = checkX(x);
const yValid = checkY(y);
return xValid && yValid;
}
当然,重构很大程度上取决于代码的语义。然而,这是将上面的示例重写为更清晰、更易读的函数的一种可能方法。
大家觉得这很熟悉吗?可能有时候我们都会这样写:
let x;
if (someVar === "something") {
x = 1;
} else {
x = somethingElse;
}
考虑一下这个重构:
let x = 1;
if (someVar !== "something") {
x = somethingElse;
}
或者是这个:
const x = someVar !== "something"
? somethingElse
: 1;
让我们看看下面这个代码
const as = document.querySelectorAll("a");
as
具体是什么类型:
首先,我们可能认为它是一个数组,但事实并非如此:
Array.isArray(as);
// false
因此,不能使用as.map(...)
。
其次,Chrome
将其显示为数组,这可能会让很多人感到困惑。但是,请注意“NodeList
”
这意味着它是所谓的“类数组对象”(或可迭代对象)。因此,每当遇到这种对象时,都可以从中创建一个数组。
const asArray = Array.from(as);
这确实是一个数组。现在,我们可以在asArray
上使用map
、filter
或任何其他数组函数。
引用可能会在代码中引起各种副作用。意识到何时处理引用以及何时仅处理值是编写无错误软件的关键。在使用对象和数组时,我们可能会遇到这些问题:
const a = { key: "value" };
const b = a;
b.key = "something else";
console.log(a.key);
// something else
b
只是对a
的引用,因此每当b
更改引用的对象键时,它也会反映在a
上。我们可以是使用下面的方法来创建一个新对象b
,而无需使用a
。
这个已经流行了一段时间了。解构会删除所有引用。
const a = { key: "value" };
const b = {...a};
b.key = "something else";
console.log(a.key); // value
console.log(b.key); // something else
解构是 Object.assign
的语法糖,因此也可以使用此技术删除引用:
const b = Object.assign({}, a);
结果与解构相同。
如果我们正在处理数组,那么可以使用Array.from
来摆脱引用:
比如
const arr1 = [1,2,3,4];
const arr2 = arr1;
arr2[0] = 5;
console.log(arr1); // [5, 2, 3, 4]
可以用Array.from
解决:
const arr1 = [1,2,3,4];
const arr2 = Array.from(arr1);
arr2[0] = 5;
console.log(arr1); // [1, 2, 3, 4]
console.log(arr2); // [5, 2, 3, 4]
当然,解构也适用于数组。另一件需要注意的事情是:Array.from
不仅适用于“类数组对象”,而且也适用于数组。
作为最后的手段,我们可以将对象字符串化并再次解析它。所有引用都将被清除。
const a = { key: "value" };
const b = JSON.parse(JSON.stringify(a));
b.key = "something else";
console.log(a.key); // value
console.log(b.key); // something else
但是,请注意 JSON.stringify 还会清除所有类型信息。这可能会给您带来日期和其他对象的一些麻烦。