清洁 JavaScript - 10 个技巧

2025-06-08

清洁 JavaScript - 10 个技巧

我们都经历过这种情况。看着一周前、一个月前、一年前的 JavaScript,我们不禁会想,自己最初编写它时喝的是什么咖啡。🤷‍♂️
很多时候,这取决于三件事:完成工作的可用时间、旧的最佳实践,或者出现了编写代码的新模式和原则。

然而,我们可以做一些经得起时间考验的事情,并且能够帮助任何接触我们代码库的人,无论是未来的我们,还是刚刚入职的初级开发人员。我整理了以下 10 条我在编写 JavaScript 时喜欢使用的技巧,以保持其简洁易读。

复杂条件句?array.some()来拯救你

好的,我们有一个 if 语句,它相当冗长。我们是否应该执行一段代码取决于很多因素。或者,这些条件是由我们应用程序中的其他逻辑动态生成的。像这样的 if 语句并不罕见:

if(condition1
  || condition2
  || condition3 === 'myEquality'
  || ...
  || conditionN.includes('truthy')) {
    // do something
  }
Enter fullscreen mode Exit fullscreen mode

这可真够棘手的!🤢
怎么解决啊!简单!数组!

const myConditions: boolean = [];
myConditions.push(condition1);
myConditions.push(condition2);
myConditions.push(condition3 === 'myEquality');
myConditions.push(conditionN.includes('truthy'));

if (myConditions.some((c) => c)) {
  // do something
}
Enter fullscreen mode Exit fullscreen mode

通过创建一个条件数组,我们可以检查其中任何一个条件是否为真,如果为真,则执行 if 语句。这也意味着,如果我们需要动态或通过循环生成条件,只需将其推送到条件数组即可。我们也可以轻松地删除条件,只需将其注释掉myCondition.push()或完全删除即可。

注意:这将创建一个数组并根据条件运行循环,因此预计会产生很小的、通常不易察觉的性能影响

数组用于“或”,但“与”又如何呢?array.every()快来试试吧!

与上面的提示几乎相同,只是不只是检查任何一个条件,而是array.every()检查每个条件是否真实!

const myConditions: boolean = [];
myConditions.push(condition1);
myConditions.push(condition2);
myConditions.push(condition3 === 'myEquality');
myConditions.push(conditionN.includes('truthy'));

if (myConditions.every((c) => c)) {
  // do something
}
Enter fullscreen mode Exit fullscreen mode

就这么简单!

没有魔法弦

不确定什么是魔法字符串?它本质上是期望输入等于任意字符串值,该字符串值可能代表也可能不代表实现,并且可能在其他地方使用,因此重构起来很困难,并且容易导致代码出错。
以下是魔法字符串的实际示例:

function myFunc(input) {
  if (input === 'myString') {
    // do something
  }
}

myFunc('myString'); // works
myFunc('myStrung'); // doesn't work
Enter fullscreen mode Exit fullscreen mode

从上面的例子中可以看出,使用myString魔法字符串很容易导致 bug。这不仅是因为开发人员的拼写错误,而且,如果你myFunc通过更改它所期望的魔法字符串来更改,那么所有调用它的函数myFunc也都需要更改,否则程序会完全崩溃:

function myFunc(input) {
  if (input === 'bar') {
    // do something
  }
}

myFunc('myString'); // no longer works
myFunc('myStrung'); // still doesn't work
Enter fullscreen mode Exit fullscreen mode

我们可以很容易地解决这个问题,但创建一个共享对象,该对象使用相应的键值设置来定义这些魔法字符串:

const MY_FUNC_ARGS = {
  DoSomething: 'bar',
};

function myFunc(input) {
  if (input === MY_FUNC_ARGS.DoSomething) {
    // do something
  }
}

myFunc(MY_FUNC_ARGS.DoSomething); // works and is refactor proof!
Enter fullscreen mode Exit fullscreen mode

在对象中定义魔法字符串不仅可以为代码提供实现上下文,还可以防止因拼写错误和重构而导致的错误!💪

数组解构返回

我不确定你的情况,但我确实有时候希望能够从一个函数返回多个值,我要么选择返回一个数组,要么选择返回一个包含这些信息的对象。有一段时间,我倾向于避免返回数组,因为我讨厌看到这样的语法:

const myResult = myFunc();

if (myResult[0] === 'yes' && myResult[1] === 2) {
  // Do something
}
Enter fullscreen mode Exit fullscreen mode

数组索引代表什么,完全没有上下文myResult,理解这里发生的事情有点困难。不过,使用数组解构,我们可以让它更具可读性🤓。看看这个:

const [userAnswer, numberOfItems] = myFunc();
if (userAnswer === 'yes' && numberOfItems === 2) {
  // Do something
  // Refactor that magic string to use an Object 🤫
}
Enter fullscreen mode Exit fullscreen mode

这难道不让工作变得容易得多吗?

对象解构返回

好的,数组解构很棒,我们可以从中了解发生了什么,但如果我们只关心函数返回的一些内容,而我们关心的内容与返回的数组的顺序不同,该怎么办?

返回一个对象可能是一个更好的解决方案,以便我们可以对其执行对象解构:

function myFunc() {
  return {
    userAnswer: 'yes',
    numberOfItems: 2,
    someKey: 10,
  };
}

const { numberOfItems, someKey } = myFunc();

if (numberOfItems === 2 || someKey === 10) {
  // Do Something
}
Enter fullscreen mode Exit fullscreen mode

现在,我们不需要关心返回数组中项目的存在顺序,并且可以安全地忽略我们关心的值之前的任何值🔥

许多文件与通用文件

也就是单一职责原则……
好吧,听我说完。有了打包工具,创建只做一件事的 JS 文件比创建几个做很多事的通用文件要容易得多,而且值得。

如果你有一个名为 的文件models.js,其中包含定义应用中所有模型结构的对象,请考虑将它们拆分成单独的文件!
请看以下示例:

一位初级开发人员正在尝试处理与添加 TODO 项相关的 API 请求。他们必须深入models.js研究 1000 行代码才能找到该AddTodoRequest对象。

初级开发人员打开data-access/todo-requests.js并查看AddTodoRequest文件顶部的内容。

我知道我更喜欢哪一个!好好想想吧。看看你的文件,看看它们是不是做了太多事情。如果是,就把这些代码拆分成一个更合适的文件。

命名你的 hack

好吧,你正在尝试做一些奇怪的事情,但没有合适的方法来实现它。也许你需要为特定的浏览器(比如IE)添加一个变通方案可能清楚
地知道你用一段专门用于此变通方案的代码做了什么,但在你之后的人可能完全不知道,甚至一个月后你也不知道。

帮自己,也帮大家一个忙,把这个解决方法说出来!做法很简单,要么把它单独拉到一个函数里,要么创建一个局部变量,并给它起个合适的名字:

function myIE11FlexWorkaround() {
  /// Workaround code
}

function main() {
  myIE11FlexWorkaround();

  const ie11CssVarsPonyFill = (() => {
    /* some pony fill code */
  })();
}
Enter fullscreen mode Exit fullscreen mode

现在,任何在你之后来的人都会确切地知道你在尝试什么!🚀

较小的方法

这毋庸置疑。我知道我们都希望方法简短,但实际上,由于时间限制,这说起来容易做起来难。但是,如果我们反过来想,如果我们正在编写单元测试,我知道我更愿意为小方法而不是大方法编写单元测试。

我更希望看到这个:

function myLargeComplexMethod() {
  const resultA = doSomePiece();
  const resultB = transformResult(resultA);
  const apiData = mapToApiData(resultB);
  const response = doApiRequest(apiData);
  return response;
}
Enter fullscreen mode Exit fullscreen mode

比起试图一次性完成所有这些独立单元的方法,我们还可以为每个较小的单元编写一些单元测试,并编写一个非常简单的测试来myLargeComplexMethod确保这些较小的单元被正确调用。我们不需要关心它们是否正常工作,因为与这些较小单元相关的单元测试已经为我们确保了这一点。

for ... of对比forEach

我想这不言而喻,但我们都经历过回调地狱,它.forEach()让我想起太多回调地狱,甚至不想去想它。而且,我们现在有了一个非常简洁的方法可以循环遍历所有类型的 Iterable,所以为什么不使用它呢?
让我们forEach()比较一下 a 和 a for ... of,然后你就可以自己做了。

const myArrayOfObjects = [{ id: 1 }, { id: 2 }, { id: 3 }];
const myMapOfObjects = new Map([
  [1, { id: 1 }],
  [2, { id: 2 }],
  [3, { id: 3 }],
]);

// forEach()

myArrayOfObjects.forEach((obj, index) => {
  // do some code
});

Array.from(myMapOfObjects.values()).forEach((obj, index) => {
  // do some code
});

// For ... of
for (const obj of myArrayOfObjects) {
  // do some code
}

for (const obj of myMapOfObjects.values()) {
  // do some code
}
Enter fullscreen mode Exit fullscreen mode

就我个人而言,我更喜欢for...of有两个原因:

  1. 你可以立即看到,其目的是循环遍历数组中的所有项目
  2. 它对于代码库中的任何可迭代对象都是一致的,无论是数组还是映射

forEach确实具有在回调中提供索引的好处,因此如果这对您有用,那么最好采用该方法。

移除try-catch方块

最后,说说我个人的一点小抱怨。try-catch块。我个人觉得它们被过度使用了,用错了地方,它们做了太多事情,或者捕获了它们原本不应该捕获的错误。这都归咎于它们的结构和外观。

我在这里对我不喜欢它们的原因有更长的描述,但简要地说,这里有一个有问题的 try-catch:

try {
  const myResult = myThrowableMethod(); // I expect this one to potentially throw
  const response = transformResult(myResult);
  const answer = doRequestThatThrowsButIWasntAware(response); // I didn't realise this could have thrown
} catch (error) {
  console.error(error); // Wait... Which method threw!?
  // do something specifc to handle error coming from myThrowableMethod
  // without expecting the error to be from a different method
}

// Ok, let me refactor so I know for certain that I'm only catching the error I'm expecting
let myResult;

try {
  myResult = myThrowableMethod();
} catch (error) {
  // do something specifc to handle error coming from myThrowableMethod
}

const response = transformResult(myResult);
const answer = doRequestThatThrowsButIWasntAware(response);
Enter fullscreen mode Exit fullscreen mode

告诉我你不认为这两者都有问题...如果你的错误处理逻辑很复杂,它只会分散读者对你的方法想要实现的目标的注意力。

我创建了一个小型库来解决这个问题:no-try。有了它,我们可以将上述代码转换为:

function handleError(error) {
  console.log(error);
}

const [myResult] = noTry(() => myThrowableMethod(), handleError);
const response = transformResult(myResult);
const answer = doRequestThatThrowsButIWasntAware(response);
Enter fullscreen mode Exit fullscreen mode

我个人觉得这样干净多了。不过这都是个人意见!


我希望您从本文中获得一些有用的提示,帮助您编写 JavaScript!

如果您有任何疑问,请随时在下面提问或在 Twitter 上联系我:@FerryColum

鏂囩珷鏉ユ簮锛�https://dev.to/coly010/clean-javascript-10-tips-37f4
PREV
清理一下:丑陋的 Try-Catches!清理一下:丑陋的 Try-Catches!
NEXT
Angular < 13:如何支持 IE11