博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
roro cam_现代JavaScript中的优雅图案:RORO
阅读量:2536 次
发布时间:2019-05-11

本文共 14488 字,大约阅读时间需要 48 分钟。

roro cam

I wrote my first few lines of JavaScript not long after the language was invented. If you told me at the time that I would one day be writing about elegant patterns in JavaScript, I would have laughed you out of the room. I thought of JavaScript as a strange little language that barely even qualified as “real programming.”

在该语言发明不久之后,我就开始编写JavaScript的前几行。 如果您当时告诉我,我有一天会写有关JavaScript中优雅模式 ,那我会在房间外大笑。 我认为JavaScript是一种奇怪的小语言,几乎没有资格被称为“真正的编程”。

Well, a lot has changed in the 20 years since then. I now see in JavaScript what saw when he wrote JavaScript: The Good Parts: “An outstanding, dynamic programming language … with enormous, expressive power.”

从那以后的20年中,发生了很多变化。 我现在可以从JavaScript中看到在写JavaScript时看到的东西:The Good Parts :“一种出色的动态编程语言,具有巨大的表达能力。”

So, without further ado, here is a wonderful little pattern I’ve been using in my code lately. I hope you come to enjoy it as much as I have.

因此,事不宜迟,这是我最近在代码中一直使用的一个很棒的小模式。 我希望你能像我一样享受它。

Please note: I’m pretty sure I did not invent any of this. Chances are I came across it in other people’s code and eventually adopted it myself.

请注意 :我很确定我没有发明任何这种东西。 我很有可能在别人的代码中遇到了它,并最终自己采用了它。

接收一个对象,返回一个对象(RORO)。 (Receive an object, return an object (RORO).)

Most of my functions now accept a single parameter of type object and many of them return or resolve to a value of type object as well.

我的大多数函数现在都接受类型为object的单个参数,并且其中许多还返回或解析为类型object的值。

Thanks in part to the destructuring feature introduced in ES2015, I’ve found this to be a powerful pattern. I’ve even given it the silly name, “RORO” because… branding? ¯\_(ツ)_/¯

部分由于ES2015中引入的解构功能,我发现这是一个强大的模式。 我什至给它取了一个愚蠢的名字“ RORO”,因为……品牌? ¯\ _(ツ)_ /¯

Note: Destructuring is one of my favorite features of modern JavaScript. We’re going to be taking advantage of it quite a bit throughout this article, so if you’re not familiar with it, here’s a quick video to get you up to speed.

注意: 分解是现代JavaScript我最喜欢的功能之一。 在整篇文章中,我们将充分利用它,因此,如果您不熟悉它,可以通过以下视频快速入门。

Here are some reasons why you’ll love this pattern:

您会喜欢此模式的一些原因如下:

  • Named parameters

    命名参数
  • Cleaner default parameters

    清理器默认参数
  • Richer return values

    更丰富的返回值
  • Easy function composition

    简单的功能组成

Let’s look at each one.

让我们看看每个。

命名参数 (Named Parameters)

Suppose we have a function that returns a list of Users in a given Role and suppose we need to provide an option for including each User’s Contact Info and another option for including Inactive Users, traditionally we might write:

假设我们有一个函数返回给定角色的用户列表,并假设我们需要提供一个用于包括每个用户的联系信息的选项和一个用于包括非活动用户的选项,传统上我们可以这样写:

function findUsersByRole (  role,   withContactInfo,   includeInactive) {...}

A call to this function might then look like:

对该函数的调用可能如下所示:

findUsersByRole(  'admin',   true,   true)

Notice how ambiguous those last two parameters are. What does “true, true” refer to?

请注意,最后两个参数的模棱两可。 “真实,真实”指的是什么?

What happens if our app almost never needs Contact Info but almost always needs Inactive Users? We have to contend with that middle parameter all the time, even though it’s not really relevant (more on that later).

如果我们的应用几乎不再需要联系信息,而几乎总是需要非活动用户,该怎么办? 即使它并不真正相关,我们也必须一直与该中间参数竞争(稍后再讨论)。

In short, this traditional approach leaves us with potentially ambiguous, noisy code that’s harder to understand and trickier to write.

简而言之,这种传统方法给我们留下了潜在的模棱两可,嘈杂的代码,这些代码更难理解和编写。

Let’s see what happens when we receive a single object instead:

让我们看看当我们收到一个对象时会发生什么:

function findUsersByRole ({  role,  withContactInfo,   includeInactive}) {...}

Notice our function looks almost identical except that we’ve put braces around our parameters. This indicates that instead of receiving three distinct parameters, our function now expects a single object with properties named role, withContactInfo, and includeInactive.

注意我们的函数看起来几乎一样,只是我们在参数上加上了大括号 。 这表明,我们的函数现在没有接收三个不同的参数,而是期望一个具有名为rolewithContactInfoincludeInactive属性的对象。

This works because of a JavaScript feature introduced in ES2015 called Destructuring.

之所以能够成功,是因为ES2015中引入了一种称为DestructuringJavaScript功能。

Now we can call our function like this:

现在我们可以像这样调用函数:

findUsersByRole({  role: 'admin',   withContactInfo: true,   includeInactive: true})

This is far less ambiguous and a lot easier to read and understand. Plus, omitting or re-ordering our parameters is no longer an issue since they are now the named properties of an object.

这远没有那么模棱两可,而且更容易阅读和理解。 另外,省略或重新排序参数不再是问题,因为它们现在是对象的命名属性。

For example, this works:

例如,这有效:

findUsersByRole({  withContactInfo: true,  role: 'admin',   includeInactive: true})

And so does this:

这样:

findUsersByRole({  role: 'admin',   includeInactive: true})

This also makes it possible to add new parameters without breaking old code.

这也使添加新参数而不破坏旧代码成为可能。

One important note here is that if we want all the parameters to be optional, in other words, if the following is a valid call…

这里的一个重要说明是,如果我们希望所有参数都是可选的,换句话说,如果以下是有效的调用,则…

findUsersByRole()

… we need to set a default value for our parameter object, like so:

…我们需要为我们的参数对象设置默认值,如下所示:

function findUsersByRole ({  role,  withContactInfo,   includeInactive} = {}) {...}

An added benefit of using destructuring for our parameter object is that it promotes immutability. When we destructure the object on its way into our function we assign the object’s properties to new variables. Changing the value of those variables will not alter the original object.

对参数对象使用解构的另一个好处是,它促进了不变性。 当我们在object进入函数的过程中对其进行解构时,我们将对象的属性分配给新变量。 更改这些变量的值不会更改原始对象。

Consider the following:

考虑以下:

const options = {  role: 'Admin',  includeInactive: true}
findUsersByRole(options)
function findUsersByRole ({  role,  withContactInfo,   includeInactive} = {}) {  role = role.toLowerCase()  console.log(role) // 'admin'  ...}
console.log(options.role) // 'Admin'

Even though we change the value of role the value of options.role remains unchanged.

即使我们更改role的值, options.role的值也保持不变。

Edit: It’s worth noting that destructuring makes a shallow copy so if any of the properties of our parameter object are of a complex type (e.g. array or object) changing them would indeed affect the original.

编辑: 值得注意的是,解构会产生浅表副本,因此,如果参数对象的任何属性属于复杂类型(例如arrayobject ),更改它们的确会影响原始object

(Hat tip to for )

(向 提示)

So far, so good, yeah?

到目前为止,一切都很好,是吗?

清理程序默认参数 (Cleaner Default Parameters)

With ES2015 JavaScript functions gained the ability to define default parameters. In fact, we recently used a default parameter when we added ={} to the parameter object on our findUsersByRole function above.

借助ES2015,JavaScript函数获得了定义默认参数的能力。 实际上,当我们在上面的findUsersByRole函数的参数对象中添加={}时,我们最近使用了默认参数。

With traditional default parameters, our findUsersByRole function might look like this.

使用传统的默认参数,我们的findUsersByRole函数可能看起来像这样。

function findUsersByRole (  role,   withContactInfo = true,   includeInactive) {...}

If we want to set includeInactive to true we have to explicitly pass undefined as the value for withContactInfo to preserve the default, like this:

如果要将includeInactive设置为true则必须显式传递undefined作为withContactInfo的值以保留默认值,如下所示:

findUsersByRole(  'Admin',   undefined,   true)

How hideous is that?

那有多可怕?

Compare it to using a parameter object like so:

将其与使用像这样的参数对象进行比较:

function findUsersByRole ({  role,  withContactInfo = true,   includeInactive} = {}) {...}

Now we can write…

现在我们可以写…

findUsersByRole({  role: ‘Admin’,  includeInactive: true})

… and our default value for withContactInfo is preserved.

…,并且保留了withContactInfo的默认值。

奖金:必填参数 (BONUS: Required Parameters)

How often have you written something like this?

您多久写一次这样的东西?

function findUsersByRole ({  role,   withContactInfo,   includeInactive} = {}) {  if (role == null) {      throw Error(...)  }  ...}

Note: We use == (double equals) above to test for both null and undefined with a single statement.

注意: 我们使用上面的== (双倍等于)来通过单个语句测试nullundefined

What if I told you that you could use default parameters to validate required parameters instead?

如果我告诉您可以使用默认参数来验证必需的参数怎么办?

First, we need to define a requiredParam() function that throws an Error.

首先,我们需要定义一个抛出Error的requiredParam()函数。

Like this:

像这样:

function requiredParam (param) {  const requiredParamError = new Error(   `Required parameter, "${param}" is missing.`  )
// preserve original stack trace  if (typeof Error.captureStackTrace === ‘function’) {    Error.captureStackTrace(      requiredParamError,       requiredParam    )  }
throw requiredParamError}

I know, I know. requiredParam doesn’t RORO. That’s why I said many of my functions — not all.

我知道我知道。 requiredParam不会RORO。 这就是为什么我说了我的许多功能-不是全部

Now, we can set an invocation of requiredParam as the default value for role, like so:

现在,我们可以设置的调用requiredParam作为默认值role ,就像这样:

function findUsersByRole ({  role = requiredParam('role'),  withContactInfo,   includeInactive} = {}) {...}

With the above code, if anyone calls findUsersByRole without supplying a role they will get an Error that says Required parameter, “role” is missing.

使用上面的代码,如果任何人findUsersByRole不提供role情况下调用findUsersByRole ,他们将收到Error ,指出Required parameter, “role” is missing.

Technically, we can use this technique with regular default parameters as well; we don’t necessarily need an object. But this trick was too useful not to mention.

从技术上讲,我们也可以将这种技术与常规的默认参数一起使用。 我们不一定需要一个对象。 但是,这个技巧太有用了,更不用说了。

更丰富的返回值 (Richer Return Values)

JavaScript functions can only return a single value. If that value is an object it can contain a lot more information.

JavaScript函数只能返回一个值。 如果该值是一个object则可以包含更多信息。

Consider a function that saves a User to a database. When that function returns an object it can provide a lot of information to the caller.

考虑将User保存到数据库的功能。 当该函数返回一个对象时,它可以为调用者提供很多信息。

For example, a common pattern is to “upsert” or “merge” data in a save function. Which means, we insert rows into a database table (if they do not already exist) or update them (if they do exist).

例如,一种常见的模式是在保存功能中“向上插入”或“合并”数据。 这意味着,我们将行插入数据库表中(如果尚不存在)或对其进行更新(如果它们确实存在)。

In such cases, it would be handy to know wether the operation performed by our Save function was an INSERT or an UPDATE. It would also be good to get an accurate representation of exactly what was stored in the database, and it would be good to know the status of the operation; did it succeed, is it pending as part of a larger transaction, did it timeout?

在这种情况下,很容易知道Save函数执行的操作是INSERT还是UPDATE 。 准确地表示存储在数据库中的内容也将是一件好事,并且知道操作状态也将是一件好事; 它成功了吗,是作为较大事务的一部分待处理吗,是否超时?

When returning an object, it’s easy to communicate all of this info at once.

返回对象时,一次即可轻松传达所有这些信息。

Something like:

就像是:

async saveUser({  upsert = true,  transaction,  ...userInfo}) {  // save to the DB  return {    operation, // e.g 'INSERT'    status, // e.g. 'Success'    saved: userInfo  }}

Technically, the above returns a Promise that resolves to an object but you get the idea.

从技术上讲,上面的方法返回了一个解决objectPromise ,但是您明白了。

简单的功能组合 (Easy Function Composition)

“Function composition is the process of combining two or more functions to produce a new function. Composing functions together is like snapping together a series of pipes for our data to flow through.” —

“功能组合是将两个或多个功能组合在一起以产生新功能的过程。 将功能组合在一起就像将一系列管道捆绑在一起以使我们的数据流过。” — (

We can compose functions together using a pipe function that looks something like this:

我们可以使用如下所示的pipe函数将函数组合在一起:

function pipe(...fns) {   return param => fns.reduce(    (result, fn) => fn(result),     param  )}

The above function takes a list of functions and returns a function that can apply the list from left to right, starting with a given parameter and then passing the result of each function in the list to the next function in the list.

上面的函数接受一个函数列表,并返回一个函数,该函数可以从左到右应用列表,从给定的参数开始,然后将列表中每个函数的结果传递到列表中的下一个函数。

Don’t worry if you’re confused, there’s an example below that should clear things up.

如果您感到困惑,请不要担心,下面有一个示例可以解决问题。

One limitation of this approach is that each function in the list must only receive a single parameter. Luckily, when we RORO that’s not a problem!

这种方法的局限性在于,列表中的每个函数只能接收一个参数。 幸运的是,当我们RORO时,这不是问题!

Here’s an example where we have a saveUser function that pipes a userInfo object through 3 separate functions that validate, normalize, and persist the user information in sequence.

这是一个示例,其中我们有一个saveUser函数,该函数通过3个单独的函数通过管道saveUser userInfo对象, saveUser函数saveUser验证,规范化和持久化用户信息。

function saveUser(userInfo) {  return pipe(    validate,    normalize,    persist  )(userInfo)}

We can use a in our validate, normalize, and persist functions to destructure only the values that each function needs and still pass everything back to the caller.

我们可以在validatenormalizepersist函数中使用 ,以仅解构每个函数所需的值,并将所有内容仍传递回调用方。

Here’s a bit of code to give you the gist of it:

以下是一些代码,可以帮助您了解要点:

function validate({  id,  firstName,  lastName,  email = requiredParam(),  username = requiredParam(),  pass = requiredParam(),  address,  ...rest}) {  // do some validation  return {    id,    firstName,    lastName,    email,    username,    pass,    address,    ...rest  }}
function normalize({  email,  username,  ...rest}) {  // do some normalizing  return {    email,    username,    ...rest  }}
async function persist({  upsert = true,  ...info}) {  // save userInfo to the DB  return {    operation,    status,    saved: info  }}

对于RO还是不对RO,这就是问题所在。 (To RO or not to RO, that is the question.)

I said at the outset, most of my functions receive an object and many of them return an object too.

一开始我说过,我的大多数函数都接收一个对象, 其中许多函数也返回一个对象。

Like any pattern, RORO should be seen as just another tool in our tool box. We use it in places where it adds value by making a list of parameters more clear and flexible and by making a return value more expressive.

像任何模式一样,RORO应该被视为我们工具箱中的另一种工具。 通过使参数列表更清晰,更灵活以及使返回值更具有表达力,我们将其用于增加价值的地方。

If you’re writing a function that will only ever need to receive a single parameter, then receiving an object is overkill. Likewise, if you’re writing a function that can communicate a clear and intuitive response to the caller by returning a simple value, there is no need to return an object.

如果编写的函数只需要接收一个参数,那么接收object就太过分了。 同样,如果您编写的函数可以通过返回一个简单的值来向调用者传达清晰直观的响应,则无需返回object

An example where I almost never RORO is assertion functions. Suppose we have a function isPositiveInteger that checks wether or not a given parameter is a positive integer, such a function likely wouldn’t benefit from RORO at all.

我几乎从不RORO的示例是断言函数。 假设我们有一个函数isPositiveInteger来检查给定的参数是否为正整数,那么该函数可能根本不会从RORO中受益。

If you enjoyed this article, please smash the applause icon a bunch of times to help spread the word. And if you want to read more stuff like this, please sign up for my Dev Mastery newsletter below.

如果您喜欢这篇文章,请多次敲击掌声图标,以帮助宣传。 如果您想阅读更多类似的内容,请在下面注册我的开发精通通讯。

翻译自:

roro cam

转载地址:http://ajwzd.baihongyu.com/

你可能感兴趣的文章
第2天线性表链式存储
查看>>
python自动化测试-D11-学习笔记之一(yaml文件,ddt)
查看>>
mysql存储过程使用游标循环插入数据
查看>>
Ubuntu 12.04 添加新用户并启用root登录
查看>>
20145309信息安全系统设计基础第9周学习总结上
查看>>
c# 字段、属性get set
查看>>
td内容超出隐藏
查看>>
Spring CommonsMultipartResolver 上传文件
查看>>
Settings app简单学习记录
查看>>
SQLAlchemy
查看>>
多线程
查看>>
使用缓存的9大误区(下)转载
查看>>
appium键值对的应用
查看>>
MyEclipse 8.X 通用算法
查看>>
selenium.Phantomjs设置浏览器请求头
查看>>
分布式数据库如何选择,几种分布式数据库优缺点一览
查看>>
BZOJ 4443: 小凸玩矩阵【二分图】
查看>>
苹果 OS X制作u盘启动盘
查看>>
Jquery便利对象
查看>>
MVC: Connection String
查看>>