7 个我最喜欢的 JavaScript 小技巧

web前端开发

共 6704字,需浏览 14分钟

 · 2021-12-19

英文 | https://medium.com/young-coder/7-of-my-favorite-little-javascript-tricks-4f2a1cfe68b4

翻译 | 杨小爱


O'Reilly 发布了第三版 JavaScript Cookbook,彻底更新了现代特性和实践。我知道这一点是因为我和我的杰出合著者亚当斯科特一起写了一些内容。
为了纪念这个版本,我决定收集一些我最喜欢的 JavaScript 片段。我不是在谈论大量的功能、框架或自动生成的代码。我也不是在谈论新的语言创新或生产力技巧。我说的是让生活更美好的智能小代码。
将 12 行缩减为 2 行,或者更容易编写干净、清晰的代码。我相信你也有自己的技巧库——也许你会在这个列表中找到一个新的想法来收集。
1、使用Symbol进行枚举
您可能熟悉 Symbol,这是一个不寻常的 JavaScript 对象,它在生活中只有一个目的:提供一个保证全局唯一的随机标识符。
Symbol 在可扩展性和元编程方面有一些有趣(且高度专业化)的应用。但它也是创建枚举的好方法——一组命名常量——JavaScript 语言本身并不支持它。
这是一个名为 TrafficLight 的类似枚举结构的示例:
// Create three constants to use as an enum const TrafficLight = { Green: Symbol('green'), Red: Symbol('red'), Yellow: Symbol('yellow')}
// This function uses the enumfunction switchLight(newLight) { if (newLight === TrafficLight.Green) { console.log('Turning light green'); } else if (newLight === TrafficLight.Yellow) { console.log('Get ready to stop'); } else { console.log('Turning light red'); } return newLight;}
// Let's try it out!let light = TrafficLight.Green;light = switchLight(TrafficLight.Yellow);light = switchLight(TrafficLight.Red);console.log(light); // shows "Symbol('red')"

在此示例中,每个枚举值(例如,TrafficLight.Green)都获得一个唯一值。但是你永远不会真正看到那个值,因为 Symbol 让它完全不透明。

因此,如果您需要在应用程序之外序列化此数据(例如,将其存储在磁盘上或通过网络发送),这可能不是您想要的方法。

2、在控制台中无痛地测试代码

引导 JavaScript 测试页面只需要几秒钟。但有时我想尝试一个单独的、离散的 JavaScript 函数。如果我可以在浏览器中处理我正在阅读的文章旁边的这个测试代码片段,那就更有用了。

现在,调用浏览器的 DevTools(Windows 上为 F12,macOS 上为 Cmd-Shift-J 或 Cmd-Option-J,具体取决于浏览器)并没有什么神奇之处。

在 JavaScript 控制台中输入一些代码并没有什么神奇之处——只需记住在每个换行符处按 Shift+Enter 并按 Enter 以运行完成的代码。

但是,如果你想迭代一个例子(输入一次,编辑它,重新运行它,等等),你需要控制你的代码执行的范围。

最好的方法是用大括号 { } 将整个代码块括起来。这样你就可以运行你的代码(按 Enter),再次调用它(按向上箭头),编辑它,然后重新运行它,所有这些都不会出现恼人的“标识符已经声明”错误。

所以不要输入这个:

let testValue = 40+12;console.log(testValue);

你要这样写:

{let testValue = 40+12;console.log(testValue);}

3、一行深度复制一个数组

您可能知道现代 JavaScript 的一项重大改进是一组函数式数组处理方法,它们使您无需迭代即可处理数据。

这些方法中最强大的方法之一是 Array.map(),它对每个元素运行一个函数,并为您提供一个带有结果的新数组。

Array.map() 可以做很多技巧,但克隆数组是更有用的技巧之一。要了解它是如何工作的,请想象您创建了一个这样的数组,其中包含两个对象:

const objectsOriginal = [{name: 'Sadie', age: 12},                         {name: 'Patrick', age: 18}];

现在假设您要复制这些对象。这段代码不是你想要的:

// All this gets you is two variables pointing to the same arrayconst objectsCopy = objectsOriginal;

这有点好,但仍然没有做你想要的:

// Creates two array objects, but they share the same people objectsconst objectsCopy = [...objectsOriginal];

(您可以通过更改一个数组中的对象并验证它是同一个更改的对象来测试这一点,即使您通过另一个数组访问它也是如此。)

现在这是一个使用 Array.map() 的解决方案,它接受每个元素,扩展对象,然后创建一个具有相同属性的重复对象:

const objectsCopy = objectsOriginal.map(element => ({...element}));

这是该技术的完整演示如下:

const objectsOriginal = [{name: 'Sadie', age: 12},                         {name: 'Patrick', age: 18}];
// Create a new array with copied objectsconst objectsCopy = objectsOriginal.map( element => ({...element}) );
// Change one of the people objects in objectsCopyobjectsCopy[0].age = 14;
// Investigate the same object in objectsOriginalconsole.log(objectsOriginal[0].age); // 12

当然,也有一些注意事项。这是一个单层深拷贝,所以如果你的对象持有对其他对象的引用,它们就不会被复制。

在这种情况下,最好通过创建自定义类并编写自定义 clone() 方法来形式化克隆逻辑。

然后你可以使用 Array.map() 来调用 clone() 方法:

const objectsCopy = objectsOriginal.map(element => element.clone());

4、一行清空一个数组

如果我们讨论数组,这里我分享一个有用的技巧。有时你想清空一个数组对象而不用一个新的空白数组替换它(可能是因为它被另一个对象引用)。

在开始迭代和调用 Array.remove() 之前,这里有一个通过设置 length 属性起作用的快捷方式:

const numbers = [2, 42, 5, 304, 1, 13];numbers.length = 0;// The array is now empty

如果您学习的是传统的 OOP 语言,这可能看起来很奇怪,因为 Array.length 似乎是一个应该只读的属性,并且设置属性通常不应该触发操作(如删除元素)。但是在 JavaScript 中,有时您只是做感觉良好的事情。

5、给你的对象一个合理的字符串表示

是否厌倦了在使用 console.log() 时,在浏览器控制台中看到“[object Object]”?您可以通过为您的对象提供一个可观的 toString() 方法来轻松覆盖此行为。下面是一个例子:

class Person {   constructor(firstName, lastName) {     this.firstName = firstName;     this.lastName = lastName;  }
toString() { return `${this.lastName}, ${this.firstName}`; }}
// Let's use our Person classconst newPerson = new Person('Luke', 'Takei'); const message = 'The name is ' + newPerson;
// Now message = 'The name is Takei, Luke'// which is much better than 'The name is [object Object]'

再一次,JavaScript 剥离了您在经典 OOP 语言中看到的一些基础设施。(例如,没有覆盖关键字。)但它有效。

6、支持类中的方法链

方法链并不是真正的技巧,但它是我们并不总是认为支持的那些实践之一,它可以为您节省一些时间。

同样重要的是,它与 JavaScript 的生活方式相契合,因为许多内置对象使用它取得了良好的效果。

考虑这个带有 Array 对象的例子。在这里,方法链允许您将两个操作合并为一行——数组连接和数组排序:

const evens = [2, 4, 6, 8];const odds = [1, 3, 5, 7, 9];const evensAndOdds = evens.concat(odds).sort();console.log(evensAndOdds); // [1, 2, 3, 4, 5, 6, 7, 8, 9]

在您自己的自定义类中支持方法链的最简单方法就是使用 this 关键字返回对当前对象的引用。这个 Book 类在它的 raisePrice() 和 releaseNewEdition() 方法中使用了这种技术:

class Book {  constructor(title, author, price, publishedDate) {     this.title = title;    this.author = author;    this.price = price;    this.publishedDate = publishedDate;  }
raisePrice(percent) { const increase = this.price*percent; this.price += Math.round(increase)/100; return this; }
releaseNewEdition() { // Set the pulishedDate to today this.publishedDate = new Date(); return this; }}
// Let's make a Book objectconst book = new Book('I Love Mathematics', 'Adam Up', 15.99, new Date(2010, 2, 2));
// Raise the price 15% and then change the edition, using method chainingconsole.log(book.raisePrice(15).releaseNewEdition());

函数式程序员会看到这个,说也许您根本不想要一个有状态的对象,而是一个不断返回副本的不可变对象,就像 Array.concat() 和 Array.sort() 做的那样。

如果这种方法对您的代码库有意义,只需在方法末尾返回一个带有修改细节的新对象,而不是当前实例,如下所示:

raisePrice(percent) {  const increase = this.price*percent;  return new Book(this.title, this.author,   this.price + Math.round(increase)/100, this.date);}

7、制作可重复的随机数列表

这个更专业一点,但它在紧要关头对我很有用。

有几种不同的方法可以在 JavaScript 中创建伪随机数。

标准 Math.random() 获取不加密安全的随机值,这适用于大多数用途。如果没有,那么鲜为人知的 Crypto.getRandomValues() 可以帮助您。

但是,这两种方法都为您提供了不可重复的随机数。

如果您想运行可重复的测试或模拟,这不是您所需要的,这对于大量统计和科学操作很重要。

这个领域变得非常深入和复杂,但我总是保持简单而快速的 Mulberry32 算法来给我完全确定性的伪随机数(这意味着如果你从相同的种子开始,你总是得到相同的列表值)。

我将它封装在一个生成器函数中,这是我最喜欢的 JavaScript 专用特性之一。

这是代码:

function* mulberry32(seed) {   let t = seed += 0x6D2B79F5;
// Generate numbers indefinitely while(true) { t = Math.imul(t ^ t >>> 15, t | 1); t ^= t + Math.imul(t ^ t >>> 7, t | 61); yield ((t ^ t >>> 14) >>> 0) / 4294967296; }}

你不需要理解这个实现的位移部分,它是从经典的 C 实现中借来的。JavaScript 的不同之处在于,这是一个生成器函数,正如 function* 关键字中的星号所表示的那样。

生成器函数使用 yield 返回按需值 — 在本例中为随机数。

以下是您创建和调用生成器的方法:

// Use the same seed to get the same sequenceconst seed = 98345;const generator = mulberry32(seed);
console.log(generator.next().value); // 0.9057375795673579console.log(generator.next().value); // 0.7620641703251749console.log(generator.next().value); // 0.0211441791616380

这个生成器函数包装了一个无限循环,只要你继续调用 next() 就会运行。如果您不需要随机数,则生成器的执行将暂停,其所有状态保持不变。

当然,您不需要生成器函数来创建随机数列表,但它是一个优雅的解决方案。

总结

如果您喜欢这些示例,请查看注意收藏学习,在此,非常感谢您的阅读,祝您编程愉快!

如果您觉得这些内容对您有帮助,请记得关注我,点赞我,并将今天的内容分享给您的朋友,也许能够帮助到他。



学习更多技能

请点击下方公众号

浏览 12
点赞
评论
收藏
分享

手机扫一扫分享

举报
评论
图片
表情
推荐
点赞
评论
收藏
分享

手机扫一扫分享

举报