5 个可能会给你带来麻烦的 JavaScript“技巧”。

2025-05-27

5 个可能会给你带来麻烦的 JavaScript“技巧”。

你有多少次看到过这样的说法:“别那样做”“这是错的”“这些技巧能让你成为专业开发者”之类的文章?😋 我不知道你是怎么想的,但我已经见识过足够多这样的文章了。别误会,很多技巧确实很有用,而且很有价值,问题不在于实现本身,而在于模仿,也就是复制/粘贴。

小费模因

让我们来看看并讨论一下其中的几个技巧。不过,在开始之前,我们先来了解一下咬伤的类型,因为它们的冲击力各不相同:

  • Readability Bite:不会直接影响你,但它会伤害到审查你代码的队友。
  • Type Bite:会咬人,使用某些类型
  • Syntax Bite:会用一定的句法表达方式

好了!不用多说了。

1. 转换为数字

这是我最喜欢的,而且我得承认我自己也一直在用它。技巧很简单,只要给任意值附加一个一元加号 (+) 运算符,就能强制将其转换为数字:


const strNum = '3645';
const strNaN = 'hi, i am not a number';

typeof +strNum; // "number"
typeof +strNaN; // "number"

+strNum; // 3645
+strNaN; // NaN

Enter fullscreen mode Exit fullscreen mode

这个技巧错误很少,并且几乎一直有效,它是许多团队建议的转换方法。

Readability Bite

我非常确定,你已经看到了它的到来 🙂 毫无疑问,任何不知道一元加运算符如何工作的开发人员都会对以下代码感到困惑:


function sum(a, b) {
  return +a + +b;
}

Enter fullscreen mode Exit fullscreen mode

更不用说我们都是函数式编程的粉丝,而这👆与它的原则不太相符。

Type Bite

不幸的是,这不适用于 2019 年推出的新数字数据类型 BigInt。


const veryBigInt = 45n;
+veryBigInt; // TypeError: Cannot convert a BigInt value to a number

Enter fullscreen mode Exit fullscreen mode

在您开始在下面的评论中抱怨之前,我很清楚您的应用程序永远不会处理这种类型,但我们都同意,不做任何假设的功能更稳定。

Solution

一个可以提高可读性、实用且能解决 BigInt 问题的解决方案:


const veryBigInt = 45n;
const strNum = '3645';
const strNaN = 'hi, i am not a number';

Number(veryBigInt); // 45
Number(strNum); // 3645
Number(strNaN); // NaN

Enter fullscreen mode Exit fullscreen mode

我没有在这里包括转换为字符串,因为从可读性的角度来看,它是一样的:


const ugly = 42 + '';
const good = String(42);
const goodToo = `${42}`;

Enter fullscreen mode Exit fullscreen mode

2. 连接数组

另一个非常流行的技巧——使用扩展运算符连接数组:


const a = [1, 2, 3];
const b = [4, 5, 6];

[...a, ...b]; // [1, 2, 3, 4, 5, 6]

Enter fullscreen mode Exit fullscreen mode

这到底是怎么回事?好吧,假设我有点喜欢这个功能,我想把它提取到函数里(因为函数式编程,你懂的🤗)。

Type Bite

以下是我们的union功能:


function union(a, b) {
  return [...a, ...b];
}

Enter fullscreen mode Exit fullscreen mode

我从一开始就有一个问题——我想要任意数量的数组的并集,而不仅仅是两个。有什么想法可以重构它,仍然使用扩展运算符吗?

第二个问题是它将包含空单元格,根据情况,这可能不是所希望的:


const a = [1, 2, 3];
const b = Array(3);
b.push(4);
union(a, b); // [1, 2, 3, undefined, undefined, undefined, 4]

Enter fullscreen mode Exit fullscreen mode

最后,我们需要非常小心地对待传递的参数union


const a = [1, 2, 3];
const b = null;
const c = 42;
const d = 'hello';

union(a, b); // TypeError: b is not iterable
union(a, c); // TypeError: c is not iterable
union(a, d); // [1, 2, 3, "h", "e", "l", "l", "o"] :/

Enter fullscreen mode Exit fullscreen mode

除此之外union,这种方法迫使您始终假设值是数组,这是一个非常大胆的假设。

Solution

让我们重写我们的函数,以便它能解决上述所有问题:


function union(...args) {
  return args.flat();
}

const a = [1, 2, 3];
const b = null;
const c = 42;
const d = 'hello';
const e = Array(3);
e.push(99);

union(a, b, c, d, e); // [1, 2, 3, null, 42, "hello", 99]

Enter fullscreen mode Exit fullscreen mode

我觉得我现在听到CS狂人冲我尖叫:“太慢了!”好吧。如果你的程序要处理超过10000个元素的数组,并且你担心性能问题,那么使用.concat()


function union(...args) {
  return [].concat(...args);
}

Enter fullscreen mode Exit fullscreen mode

这种方式性能更高一些,但会抓取空单元格。无论如何,处理空单元格的可能性非常小👍

我想说的是,这个.concat()方法并没有过时,你不应该这样对待它。此外,使用函数而不是运算符会让你的程序更稳定一些。

3. 使用按位运算符对数字进行舍入。

位运算符的底层特性使其运行速度非常快,而且你不得不承认它们相当“书呆子”,我知道有多少人会被它们吸引🤓。当然,任何位运算符都会导致可读性下降,我们甚至不会讨论它。

让我们回到“舍入”的话题。你可能会注意到,不同的人会用不同的运算符来实现舍入,常用的运算符是按位或|和双按位非~~。实际上,你可以使用所有这些运算符:


const third = 33.33;
/* Bitwise AND */
third & -1; // 33

/* Bitwise NOT */
~~third; // 33

/* Bitwise OR */
third | 0; // 33

/* Bitwise XOR */
third ^ 0; // 33

/* Left shift */
third << 0; // 33

/* Right shift */
third >> 0; // 33

/* Zero fill right shift (positive numbers only) */
third >>> 0; // 33

Enter fullscreen mode Exit fullscreen mode

怎么回事?!简直难以置信,不是吗?嗯,是的。你并没有“四舍五入”,只是用位运算符返回相同的数字。考虑到位运算符只能对 32 位整数进行操作,这实际上会截断浮点数,因为它们不在 32 位范围内。这让我们……

Syntax Bite

32 位整数的范围是从-2,147,483,648+2,147,483,647。这听起来可能很多,但实际上这可能是 Justin Bieber 在 YouTube 上的平均视频数量。正如你可能猜到的那样,超出这个范围就行不通了:


const averageBieberViewsCount = 2147483648.475;
averageBieberViewsCount | 0; // -2147483648 🥲
~~averageBieberViewsCount; // -2147483648 🥲

Enter fullscreen mode Exit fullscreen mode

除此之外,它首先不是进行四舍五入,而是截断数字的小数部分:


const almostOne = 0.9999999;
almostOne | 0; // 0 :/

Enter fullscreen mode Exit fullscreen mode

最后,这种方法具有奇怪的关系NaN,可能会导致非常严重的错误:


~~NaN; // 0

Enter fullscreen mode Exit fullscreen mode

Solution

只需使用为此构建的函数:


const third = 33.33;
const averageBieberViewsCount = 2147483648.475;
const almostOne = 0.9999999;

Math.round(third); // 33
Math.round(averageBieberViewsCount); // 2147483648
Math.round(almostOne); // 1
Math.round(NaN); // NaN

Enter fullscreen mode Exit fullscreen mode

4. 使用 Number.toFixed 进行舍入

当我们讨论四舍五入这个话题时,让我们再看一个非常流行的方法,特别是在处理任何与货币相关的数字时:


const number = 100 / 3;
const amount = number.toFixed(2); // "33.33"

Enter fullscreen mode Exit fullscreen mode

任何编程语言中的浮点数都是一个问题,不幸的是,JavaScript.toFixed()也不例外。

Syntax Bite

当要舍入的最后一位数字为 5 时,会出现舍入边缘情况,根据舍入规则,这种情况应向上舍入,因此:


(1.5).toFixed(0); // 2 👍
(1.25).toFixed(1); // 1.3 👍
(1.725).toFixed(2); // 1.73 👍
/* and so on */

Enter fullscreen mode Exit fullscreen mode

不幸的是,情况并不总是如此:


(0.15).toFixed(1); // 0.1 👎
(6.55).toFixed(1); // 6.5 👎
(1.605).toFixed(2); // 1.60 👎

Enter fullscreen mode Exit fullscreen mode

正如您所看到的,我们在这里并不是在讨论四舍五入到极端精度,四舍五入到小数点后一位或两位是正常的日常工作。

Solution

解决方案之一是使用第三方的精度舍入函数,例如_.round()或类似的函数。或者,你也可以自己编写类似的函数,这也不是什么难事🚀:


function round(number, precision = 0) {
  const factor = 10 ** precision;
  const product = Math.round(number * factor * 10) / 10;
  return Math.round(product) / factor;
}

round(0.15, 1); // 0.2 👍
round(6.55, 1); // 6.6 👍
round(1.605, 2); // 1.61 👍

Enter fullscreen mode Exit fullscreen mode

这种函数的一个很酷的副产品是,你可以立即获得负精度舍入,也就是尾随零的数量:


round(12345, -3); // 12000
round(12345, -2); // 12300
round(12345, -1); // 12350
round(-2025, -1); // -2020

Enter fullscreen mode Exit fullscreen mode

5. 高阶方法“捷径”

另一个非常流行的技巧是使用预先构建的函数作为高阶方法(需要函数作为参数的方法)的参数,它与.map()和配合使用效果非常好.filter()


const randomStuff = [5, null, false, -3, '65'];

/* Convert to string */
randomStuff.map(String); // ["5", "null", "false", "-3", "65"]

/* Convert to number */
randomStuff.map(Number); // [5, 0, 0, -3, 65]

/* Filter out falsy values */
randomStuff.filter(Boolean); // [5, -3, "65"]

/* Falsy check */
!randomStuff.every(Boolean); // true

Enter fullscreen mode Exit fullscreen mode

你明白我的意思了...超级黑客,超级酷😎

Syntax Bite

假设我需要解析一些 CSS 边距值,这是一项非常合理的任务:


const margin = '12px 15px';
const parsedMargin = margin.split(/\s+/).map(parseInt);

console.log(parsedMargin); // [12, NaN] :/

Enter fullscreen mode Exit fullscreen mode

每个高阶方法都会调用一个给定的函数,并传递三个参数:元素、索引和原始数组的引用。实际上,在方法的每次迭代中,parseInt函数都会被赋予至少两个参数,而这正是预期的参数数量parseInt:待解析的字符串和基数,因此我们最终将元素的索引作为基数传递:


/* Iteration #1 */
parseInt('12px', 0); // Radix 0 is ignored and we get 12

/* Iteration #2 */
parseInt('15px', 1); // Radix 1 doesn't exists and we get NaN

Enter fullscreen mode Exit fullscreen mode

Solution

您可以随时检查要使用的函数需要多少个参数.length,如果多于 1 个,那么将此函数作为参数传递可能不安全,而是需要对其进行包装:


parseInt.length; // 2

const parsedMargin = margin
  .split(/\s+/)
  .map((margin) => parseInt(margin));

console.log(parsedMargin); // [12, 15] 🎉🎉🎉

Enter fullscreen mode Exit fullscreen mode

结论

问自己一个问题……

不要盲目地遵循网上写的内容,要质疑自己,研究、测试,然后再反复研究、反复测试。“它就是有效”永远不应该成为借口!如果你不知道它为什么有效,那就假设它无效。

我其实为这篇文章准备了10个技巧,但感觉篇幅太长,代码量太大,一篇就写不完,所以除非你在评论区把我彻底打败,否则我可能很快就会再发一篇后续文章。说到评论,欢迎随时讨论,如果你之前遇到过什么让你头疼的技巧和窍门,请告诉我。

2021年新年快乐!

文章来源:https://dev.to/snigo/5-javascript-tips-that-might-bite-you-back-2gie
PREV
成为一名优秀的程序员:心态和学习策略
NEXT
如何在 15 分钟内将 Husky、ESLint、Prettier 集成到项目中(分步指南)