像许多其他开发者一样,我正在继续我的教育,学习关于 ES6 的知识。我做这件事的一种方法是参加由聪明人举办的研讨会。我参加了 Kyle Simpson 的 ES6: The Good Parts 课程,并发现自己对以前没有注意到的一块 ES6 的实际应用特别感兴趣:解构对象作为参数。
一些背景
随着应用程序的增长,我们的函数参数可能会变得臃肿。这会带来许多问题
- 我们必须记住调用函数时这些参数的特定顺序。如果超过三个,则需要进行一些上下文切换才能进行比较。
- 我们没有开箱即用的默认值
- 为了进行适当的自文档化,我们严重依赖于命名。在代码审查中命名参数真是一件趣事!
我们如何用 ES6 解决这个问题?
从广义上讲,考虑将对象作为参数传递,乍一看似乎很简单,甚至可能稍微复杂一些,因为人们不习惯以这种方式阅读参数并思考它们。但是看看上面解决的所有问题
- 您不再需要记住特定的顺序
- 我们可以轻松拥有可选参数
- 我们可以有默认值,这也很棒,因为它可以自我文档化
- 我们可以为失败条件设置回退,或者如果我们想显示错误,可以选择不设置
解构可以帮助我们轻松地从数组或对象中提取和操作数据到不同的变量,并使我们的参数更有意义。这有助于我们在将来与读者沟通我们的意图,并有助于维护。
什么是解构?
解构对象作为参数将几个东西组合成一个。在我们展示这个特定的应用之前,让我们先从整体上了解解构的概念。解构有很多用途和应用,可能最常用于当你有一个大型 JSON 对象或 API,并且你想从中提取有意义的变量时。解构在 ES5 中是可能的,但是由于 ES6 中的许多功能组合,解构变得更加清晰和有价值。
我们只讨论解构对象(而不是数组),但需要注意的是,两者都是可能的。我们将从 ES5 和 ES6 中对象解构的非常基本和简化的比较开始
以下是如何在 ES5 中解构对象
var o = {a: 1, b: 2, c: 3};
var chico = o.a,
harpo = o.b,
groucho = o.c;
console.log(chico, harpo, groucho);
// 1 2 3
现在,它的 ES6 对应物
const o = {chico: 1, harpo: 2, groucho: 3};
const { chico, harpo, groucho } = o;
console.log(chico, harpo, groucho);
// 1 2 3
这有一些非常有用的实际应用。我们可以从对象创建顶层变量,一次创建几个属性,甚至在它们嵌套了几层深的时候提取它们。
const o = {
chico: 1,
harpo: 2,
groucho: 3,
othermarx : {
zeppo: 4,
isthatamarxbrother: {
gummo: 5,
polly: 6
}
}
};
const { gummo, polly } = o.othermarx.isthatamarxbrother;
console.log(gummo, polly);
我们甚至可以在这个过程中重命名它们,例如 const { gummo, polly:cousin } = o.othermarx.isthatamarxbrother;
本文不会介绍解构的所有内容,以便我们能够专注于一个实际的应用。如果您有兴趣了解更多信息,以及它如何应用于 ES6 中的数组以及其他一些不错的用途,以下是一些不错的资源
我们如何在函数中使用它?
让我们从一个非常基础的示例开始,该示例是一个带有某些参数的函数。假设我们需要一个包含大量标准装运数据的清单。这些数据在大多数情况下保持不变,但每天都会有一些偏差。默认值在这里很有意义,但我们必须写出类似这样的东西
function shipmentES5Defaults(params) {
params = params === undefined ? {} : params;
var items = params.items === undefined ? 'bananas' : params.items;
var number = params.number === undefined ? 5: params.number;
var pkg = params.pkg === undefined ? 'crates' : params.pkg;
console.log("We have a shipment of " + items + " in " + number + " " + pkg + ".");
}
shipmentES5Defaults();
// We have a shipment of bananas in 5 crates.
shipmentES5Defaults({
items: 'cherries',
pkg: 'bags'
});
// We have a shipment of cherries in 5 bags.
这很好,因为我们有默认值,并且如果未指定某些内容,则不会返回未定义。
但是在 ES6 中,我们可以使用刚刚介绍的解构对象作为参数,这为我们提供了一种快速简便的方法来提供默认值。除了有用之外,它也是一种自我文档化的方法。这非常有用,并且是一种非常好的在可读性方面编写函数的方法。更不用说 ES6 模板字面量 使字符串易于阅读和写入。
function shipmentES6({ items = 'bananas', number = 5, package = 'boxes' } = {}) {
console.log(`We have a shipment of ${items} in ${number} ${package}.`);
};
shipmentES6({ package: 'crates' });
// -> We have a shipment of bananas in 5 crates.
shipmentES6({ items: 'tomatoes', number: 18 });
// -> We have a shipment of tomatoes in 18 boxes.
shipmentES6();
// -> We have a shipment of bananas in 5 boxes.
除了所有这些好处之外,如果您和您的同事或合作者习惯以这种风格编写,那么如果您不传递默认值,您还在传达没有默认值,并且您希望它出错。
敬请关注我们的下一篇文章,我们将使用这种编写参数的方法来构建一个没有外部库的 SVG 图表。
为什么你写
而不是
我敢打赌你会觉得它很令人困惑。ES6 提供了一些有趣的点,但 IMO 太混乱了,乍一看并不容易理解。
您还可以定义一个默认对象,该对象位于函数外部,用作所有默认装运函数。将它们传递到函数内部并定义许多参数会导致函数变得过长。
呃……无法编辑自己的帖子?
无论如何,params.number || 5 中存在一个小问题,因为传递 0 被视为假值,因此将使用 5。
我认为你之所以觉得它很令人困惑,是因为你不习惯它。相信我,我也遇到过这种情况。然后我给了它一个机会,现在感觉完全自然和容易理解。在使用 ES5 应用程序时,我非常想念解构。
因为它们并不相同:如果
params.number
为 0,它将被忽略,并且将默认为 5。this || that
技巧只在你不期望假值时有效。这就是函数应该在所有地方工作的方式。一旦你看到它,它似乎是显而易见的
我明白 ES6 如何让一些事情变得更容易,但我不会将其用作结构化参数的完整替代品。
我发现了一种介于纯解构和静态参数之间的折衷方案,这种方案是我在多年前使用(请不要嘲笑我)Adobe Flash 时获得的。它结合了结构化和解构。
例如,任何必需的参数都是第一个和第二个参数,依此类推。通常按照重要性排序。这些参数根本不能有默认值,而需要传递给函数才能正常执行。
然后,任何可选参数(我可能为其设置默认值)都将作为最后一个参数传递。然后,将其与默认值组合。
(请原谅 jQuery,它只是演示这一点的最简单方法)
使用 ES6,我只需放弃 jQuery 的 .extend() 函数,或我自己的选项合并系统。但这并不意味着我会完全使用非结构化参数。
使用混合技术允许我在不更改参数的情况下扩展或删除功能。我发现这在您工作超过 10 年的代码库中是必要的。
这还提供了有关缺少必需字段的错误的即时反馈。完全非结构化参数无法做到这一点。仅使用非结构化参数会迫使您在每个函数中滚动自己的错误检查,以检查缺少的必需参数。(除非我遗漏了 ES6 处理默认值的方式中的一些神奇功能?)
在 ES6 中,您可以不用 jQuery。
Rahul: 你描述的功能还没有包含在任何规范中,它仍然处于第 2 阶段 https://github.com/sebmarkbage/ecmascript-rest-spread/blob/master/README.md
我认为它很有用。谢谢。
当它首次在 CoffeeScript 中引入时,我非常喜欢这种模式。但是,最近我开始把它看作一种代码味道。
第一个问题:使用对象解构使您的多参数函数只能接受一个参数(对象散列)。这使得您的函数难以组合,因为它们无法被部分应用。
第二个问题:有太多参数,以至于顺序很容易忘记(或者有很多可选参数),这表明函数太大,违反了单一职责原则。或者,一些参数是紧密相关的,应该一起传递。(在这些对基本类型痴迷的参数的掩盖下,可能隐藏着一个值对象。)
最后,如果您真的有很多不相关的参数,并且函数真正专注,那么您可能拥有的就是一个选项对象,而不是一堆松散相关的参数。如果您有一个选项对象,您可能希望将其与另一个配置对象合并,或使用 `_.defaults` 应用默认值;在这种情况下,将结构分解为单独的变量就失去了意义。
这确实是一个选项对象。该对象随后被解构,但它最初是一个。
此外,您的默认对象也在函数参数中定义,因此您将三个步骤合而为一:参数定义、默认值定义、默认值应用。
您可以设置一个抛出错误的函数作为默认参数,以标记必选项。
文章中演示的示例绝对不是选项,因为它们不会改变函数的行为方式。它们是函数操作的数据。
例如,一个接收要打印的消息的函数。消息不是选项,而是数据。选项是是否应该将消息着色或格式化。显然,这种区分相当依赖于上下文。但一种启发式方法是参数/选项是否可以通过配置文件以有意义的方式提供。(如果不是,它们可能不是选项!)
尽管如此,公允地说,我只是假设其他人和我一样认为选项不应该是自由浮动的变量,而应该绑定到选项对象以确保清晰度。因此,使“选项”情况与我概述的前两种情况区别开来。
语法很好,我当然喜欢结构分解。但大多数情况下,我发现它的使用是函数定义设计不佳的信号。
我不得不赞同 Jason 的代码味道观点。它是一个很棒的工具箱工具,但应该谨慎使用。文章中的示例写成这样要好得多
为什么要默认设置 item 参数?这显然只是一个例子,但有时人们会看到这样的例子,就认为这是“现在应该这样做的方式”。
感谢这个例子!命名参数很棒,使调用函数的代码更加清晰。一个(微不足道?)的缺点是,当一个函数接受一个对象而不是一组参数时,您不能(轻松地)部分应用参数到该函数。
你的
hat
很糟糕。@Gandalf – 感谢分享,正是这些类型的评论让网络变得很棒。