命令式 声明式_从命令式到声明式JavaScript

命令式 声明式

介绍 (Introduction)

In this post, I will explain why declarative code is better than imperative code.

在这篇文章中,我将解释为什么声明性代码比命令性代码更好。

Then I will list some techniques to convert imperative JavaScript to a declarative one in common situations, defining key terms along the way.

然后,我将列出一些在常见情况下将命令式JavaScript转换为声明式JavaScript的技术,并在此过程中定义关键术语。

为什么要声明式? (Why declarative ?)

First, let’s define what declarative and imperative mean.

首先,让我们定义声明式命令式的含义。

Declarative code is one that highlights the intent of what it’s doing.

声明性代码是一种突出其工作意图的代码。

It favors the “what” over the “how”.

它偏向于“什么”而不是“如何”。

In other words, the exact implementations actually doing the work (aka the “how”) are hidden in order to convey what that work actually is (aka the “what”).

换句话说,隐藏实际执行工作的确切实现(也称为“如何”),以便传达该工作实际是什么(即“什么”)。

At the opposite, imperative code is one that favors the “how” over the “what”.

相反, 命令性代码是一种偏爱“如何”而不是“什么”的代码。

Let’s see an example:

让我们来看一个例子:

The snippet below perform two things: it computes the square of x, then check if the result is even or not.

下面的代码片段执行两件事:计算x的平方,然后检查结果是否为偶数。

Here, we can see that we finally get isEven after several steps that we must follow in order.

在这里,我们可以看到,在必须按顺序执行几个步骤之后,我们终于得到了isEven

These steps describe “how” we arrive to know if the square of x is even, but that's not obvious.

这些步骤描述了我们如何知道x的平方是否为偶数,但这并不明显。

If you take a non-programmer and show him this, he might have a hard time deciphering it.

如果您选择非程序员并向他展示,那么他可能很难理解它。

Now let’s see another snippet where I introduce a magic isSquareEven function that performs the two same things than the previous one.

现在,让我们看看另一个片段,其中引入了一个神奇的isSquareEven函数,该函数执行与上一个相同的两个操作。

Much more concise right ?

更简洁吧?

The result we’ll get is obvious because of the name of the function.

由于函数的名称,我们将获得明显的结果。

Even a non-programmer could easily understand.

即使是非程序员也很容易理解。

But I’m cheating. I used a magic function.

但是我在作弊。 我使用了魔术功能。

We don’t see “how” it actually works.

我们没有看到它实际上是如何工作的。

And that’s a good thing because that frees us time, energy and memory to focus on what the piece of code does at the end of the day.

这是一件好事,因为这使我们腾出了时间,精力和内存,将精力集中在一天结束时代码段所做的事情上。

If we just want to use it, that’s ideal.

如果我们只想使用它,那是理想的选择。

But if we’re curious, we still have the possibility to be by going to the definition of the function. (most editors allows you to jump to it easily)

但是如果我们很好奇,我们仍然可以通过定义函数来实现。 (大多数编辑器使您可以轻松跳转到它)

So let’s see the implementation of isSquareEven (the "how"):

因此,让我们看一下isSquareEven的实现(“方式”):

The fundamental building blocks of declarative code are functions.

声明性代码的基本构建块是函数。

In JavaScript, they have the particularity to be “first-class”.

在JavaScript中,它们具有“一流”的特殊性。

Which means that we can use them as normal data structures like numbers, strings etc.

这意味着我们可以将它们用作常规数据结构,例如数字,字符串等。

In other words, you can store a function in a variable. You can pass a function as argument to another function. And a function can return another function.

换句话说,您可以将函数存储在变量中。 您可以将一个函数作为参数传递给另一个函数。 一个函数可以返回另一个函数。

So now that we have defined these two terms, it’s easier to see in what declarative code is better than imperative code.

因此,既然我们已经定义了这两个术语,那么在哪种声明性代码比命令性代码更好的方面就更容易看到。

Declarative code is more readable.

声明性代码更具可读性。

Like we saw, it’s way more concise and easier to understand.

如我们所见,它更简洁,更容易理解。

In the previous example, we didn’t deal with that much code so both snippets were easily understandable.

在前面的示例中,我们没有处理那么多代码,因此这两个代码片段都易于理解。

But you can imagine that a real project contains more code, and thus, more complexity.

但是您可以想象一个真实的项目包含更多的代码,因此也更加复杂。

So optimizing the readability will be more and more helpful as the codebase grows, and that’s even more important if multiple persons work on it.

因此,随着代码库的增长,优化可读性将变得越来越有帮助,而如果有多个人在上面工作的话,这一点尤为重要。

Declarative code is reusable. (because it often implies functions)

声明性代码是可重用的。 (因为它通常暗示功能)

You may have noticed in the first snippet (imperative one) that the value of x is hard-coded at the beginning.

您可能已经在第一个代码段( 命令式代码)中注意到x的值在开始时是硬编码的。

If we don’t do this, the following lines cannot work.

如果我们不这样做,那么以下几行将无法工作。

Instead, when we use a function like in the declarative snippet, we can reuse the logic for any input, as long as it is a number.

相反,当我们在声明性代码段中使用类似的功能时,只要它是一个数字,就可以对任何输入重用该逻辑。

技巧 (Techniques)

These techniques concern control flow statements: if/else, while, for loops.

这些技术涉及控制流语句:if / else,while,for循环。

These are imperative.

这些势在必行。

They describe how the work is done instead of what it is.

他们描述了工作是如何完成的,而不是工作是什么。

As a general guideline, you can simply abstract a piece of logic into a function with a descriptive name in order to make your code more declarative.

作为一般准则,您可以将一段逻辑简单地抽象为一个具有描述性名称的函数,以使您的代码更具声明性。

But when it comes to control flow statements, what to do is less obvious.

但是当涉及到控制流语句时,要做的事情并不那么明显。

如果/其他语句 (If/else statements)

Sometimes, if/else statements are pretty explicit and short so I would argue that staying with them is, in this case, legitimate.

有时, if/else语句非常明确且简短,因此我认为在这种情况下,与它们保持在一起是合理的。

But other times, their conditions are less obvious and/or longer.

但是其他时候,它们的状况不太明显和/或更长。

So we might abstract them out into functions with declarative names.

因此,我们可以将它们抽象为具有声明性名称的函数。

For example, let’s say we want to check if an object is empty:

例如,假设我们要检查一个对象是否为空:

https://gist.github.com/e0be9a60a4b4dfb0bca1a46aaad85168

https://gist.github.com/e0be9a60a4b4dfb0bca1a46aaad85168

In JavaScript, there’s no easy way to determine if an object is empty.

在JavaScript中,没有简单的方法来确定对象是否为空。

You may be inclined to do this:

您可能倾向于这样做:

But the condition will evaluate to false, because when using the == or === signs with objects (including arrays, functions, classes), they're compared by reference, not by equality of their properties.

但是条件将评估为false,因为当将=====符号与对象(包括数组,函数,类)一起使用时,它们是通过引用而不是通过属性相等来比较的。

If you’re not familiar with this mechanism, explore this.

如果您不熟悉此机制,请探索this

Going back to our example, it takes a bit of time realising that Object.keys(o).length === 0 is a trick to check if an object is empty or not.

回到我们的示例,花一点时间才能意识到Object.keys(o).length === 0是一种检查对象是否为空的技巧。

So we might facilitate this by encapsulating it in a function:

因此,我们可以通过将其封装在一个函数中来简化此过程:

Now we want to log “Object is empty.” or “Object is not empty.” based on the result.

现在我们要记录“对象为空”。 或“对象不为空”。 根据结果​​。

Instead of using an if/else statement, we can use a ternary expression.

除了使用if/else语句,我们还可以使用三元表达式

The difference between the two, apart from the syntax, boils down to the difference between a statement and an expression.

除语法外,两者之间的区别归结为语句表达式之间的区别。

A statement evaluates to nothing whereas an expression always evaluates to a value.

语句的计算结果为空,而表达式的计算结果始终为值。

So we can treat an expression as a value, which means that we can store it in a variable, pass it into a function etc.

因此,我们可以将表达式视为值,这意味着我们可以将其存储在变量中,并将其传递给函数等。

You can’t do that with an if/else statement, for example:

您不能使用if/else语句来做到这一点,例如:

Finally, converting our first imperative snippet into a declarative one using a function and a ternary expression:

最后,使用函数和三元表达式将我们的第一个命令式代码片段转换为声明 代码片段:

You might put the result of the ternary expression into a variable for even more readability and/or to reuse it later.

您可以将三元表达式的结果放入变量中,以提高可读性和/或稍后重用它。

对于循环 (For loops)

When you’re tempted to use a for loop, you can use map or forEach instead.

当您想使用for循环时,可以改用mapforEach

You might need to convert your data into an array if it isn’t already because those functions only apply on it.

如果尚未将数据转换为数组,则可能需要将其转换为数组,因为这些函数仅适用于该数组。

For example, you can use Object.entries() to get an array of key-value pairs sub-arrays from an object.

例如,可以使用Object.entries()从对象获取键值对子数组的数组。

Then, after the mapping, you can convert back your result into an object with Object.fromEntries().

然后,在映射之后,您可以使用Object.fromEntries()将结果转换回一个对象。

forEach is usually used for side-effects while map must be pure.

forEach通常用于产生副作用,而map必须是纯净的。

In other words, you use map when you need to get an array back whereas you use forEach if you want to perform some action(s) but don't care if it returns something or not.

换句话说,当您需要取回数组时,可以使用map ;而如果要执行某些操作,则可以使用forEach ,但不在乎它是否返回某些内容。

That’s why forEach returns undefined.

这就是为什么forEach返回undefined的原因。

Here’s an example of what I said:

这是我说的一个例子:

Now let’s see what it gives us with map and forEach:

现在,让我们看看mapforEach给我们带来了什么:

We could achieve the same result simply by creating the “presentation” string inside the forEach callback and logging it right after, but I wanted to show the use cases of both map and forEach.

我们可以简单地通过在forEach回调内部创建“ presentation”字符串并将其立即记录下来来达到相同的结果,但是我想展示mapforEach的用例。

Now you might say:

现在您可能会说:

Ok. But what if I want, let’s say, the sum of all ages ?

好。 但是,如果我想说所有年龄段的总和呢?

With a for loop, that's pretty straightforward:

使用for循环,这非常简单:

What how to implement that with map ?

map如何实现呢?

We can’t because it always gives us an array and here, we want a number.

我们不能,因为它总是给我们一个数组,在这里,我们想要一个数字。

Now we need another function: reduce:

现在我们需要另一个功能: reduce

I’m not gonna explain how reduce works here but that's quite a powerful tool you must learn because it's just... amazing. (I'll put some resources at the end of the post if you want to.)

我不会在这里解释reduce工作原理,但这是您必须学习的功能非常强大的工具,因为它只是……令人惊叹。 (如果您愿意,我会在帖子末尾添加一些资源。)

Anytime you want to compute whatever (an other array, an object, a number, a string etc.) from an array, you can use it.

任何时候只要您要计算数组中的任何内容(其他数组,对象,数字,字符串等),都可以使用它。

In fact, you can implement map, filter, slice and certainly other "declarative" functions with it.

实际上,您可以使用它实现mapfilterslice和其他肯定的“声明性”功能。

That’s why it’s harder to grasp at the beginning.

这就是为什么一开始很难掌握的原因。

But is there a non-trivial instrument that needs no learning before producing beautiful musics with ?

但是,有没有一种平凡的乐器可以在制作优美的音乐之前不需要学习?

I don’t think so.

我不这么认为。

Like I mentionned, there are other handy functions like filter and slice that can replace for loops to do specific tasks. (Again, the ressources are at the end of the post.)

就像我mentionned,还有像其他方便的功能, filterslice ,可以取代for循环来执行特定任务。 (同样,资源在帖子的结尾。)

Ok ! Let’s tackle the last technique.

好 ! 让我们解决最后一种技术。

While循环 (While loops)

While loops can be replaced by recursion, more precisely, recursive functions.

虽然可以用递归来代替循环,更确切地说,可以用递归函数代替。

What the heck is that ?

那是什么呀 ?

Well. A recursive function has two caracteristics:

好。 递归函数具有两个特征:

  1. It calls itself (recursive case).

    它自称(递归的情况)。
  2. It as a stop condition (base case) that, when satisfied, unwinds the callstack and eventually returns the wanted value.

    作为停止条件(基本情况),该条件在满足时将展开调用栈并最终返回所需的值。

Let’s see an example:

让我们来看一个例子:

Here’s a visualization of what happens:

这是发生的情况的可视化:

Image for post

Each black arrow can be replaced by “returns”.

每个黑色箭头都可以用“返回”代替。

The whole process explained literally:

整个过程从字面上解释:

  1. foo(16): x === 16 so x > 5, thus we return foo(16 / 2). So we call it and will return whatever this call returns.

    foo(16):x === 16所以x> 5,因此我们返回foo(16 / 2) 。 因此,我们将其调用,并将返回此调用返回的结果。

  2. foo(8): x === 8 so x > 5, thus we return foo(8 / 2). So we call it and will return whatever this call returns.

    foo(8):x === 8所以x> 5,因此我们返回foo(8 / 2) 。 因此,我们将其调用,并将返回此调用返回的结果。

  3. foo(4): x === 4 so x < 5, the stop condition is satisfied. Thus we return 4.

    foo(4):x === 4因此x <5,满足停止条件。 因此,我们返回4。
  4. Going back to step 2, we wanted to return whatever foo(8 / 2) (aka foo(4) in step 3) returns. Now we know it's 4, we return 4.

    回到第2步,我们想返回foo(8 / 2) (8/2 foo(8 / 2) (在第3步中又称为foo(4))返回的值。 现在我们知道它是4,我们返回4。

  5. Going back to step 1, we wanted to return whatever foo(16 / 2) (aka foo(8) in step 2) returns. Now we know it's 4, we return 4.

    回到步骤1,我们想返回任何foo(16 / 2) (16/2 foo(16 / 2) (在步骤2中又称为foo(8))。 现在我们知道它是4,我们返回4。

  6. Since the original call was foo(16), we have 4 as result.

    由于原始调用为foo(16) ,因此结果为4。

The example is rather simple but at the end of the day, every recursive function share the two caracteristics I’ve listed above.

这个例子很简单,但是归根结底,每个递归函数都具有上面列出的两种特性。

That’s a technique I really struggled to grasp, so if it’s your case, don’t give up and expose yourself regularly to different learning materials.

我确实很难掌握这项技术,因此,如果是您的情况,请不要放弃并定期让自己接触其他学习材料。

Eventually, that should click one day.

最终,应该点击一天。

Now let’s see an example where we convert a while loop into a recursive function:

现在,让我们看一个将while循环转换为递归函数的示例:

Now, can you tell me what this loop does ?

现在,您能告诉我这个循环是做什么的吗?

It computes the factorial of 5.

它计算5的阶乘。

So at the end, x is 120.

所以最后x是120

Even in this fairly simple example, it’s not obvious what the while loop does.

即使在这个相当简单的示例中, while循环的作用也不是很明显。

Using a recursive function, we can easily solve this problem by giving it a meaningful name:

使用递归函数,我们可以通过为其赋予一个有意义的名称来轻松解决此问题:

Beautiful right ?

美丽吧?

Try to decompose the function like I did in the previous visualization.

尝试像以前的可视化一样分解函数。

Do it on paper to really engage with the information.

用纸做,真正地与信息互动。

更进一步 (Going further)

Learning `reduce`:

学习`reduce`:

Learning recursion:

学习递归:

filter

过滤

slice

In this post, I highlighted the terms imperative and declarative, essentially declaring that the later gives us more readable code.

在这篇文章中,我强调了命令式声明 这两个术语,从本质上讲,后者使我们更具可读性。

But often those term are used in the context of OOP (often imperative) vs FP (more declarative).

但是,这些术语通常在OOP(通常是命令性的)与FP(更具声明性)的上下文中使用。

Those are two programming paradigms (that is way of tackling software problems if you will).

这是两个编程范例(如果可以的话,这就是解决软件问题的方法)。

FP stands for Functional Programming and OOP for Object-Oriented Programming.

FP代表功能编程,而OOP代表面向对象的编程。

While for loops are not an "Object-Oriented" thing specifically, the higher-order functions I introduced (map, reduce etc) are core tools in Functional Programming, for which they're often attributed.

尽管for循环不是专门针对“面向对象”的事物,但我介绍的高阶函数 (映射,reduce等)是函数式编程中的核心工具,通常将其归因于它们。

The functional paradigm really interests me and I think you can enjoy learn about it.

功能范例让我非常感兴趣,我认为您可以喜欢它。

In addition to being more declarative, it offers other powerful ideas/features like immutability of data-structures which helps to avoid a whole set of bugs related to mutability.

除了更具声明性之外,它还提供了其他强大的思想/功能,例如数据结构的不变性,这有助于避免与可变性相关的整套错误。

Unfortunately, JS doesn’t provide that out of the box so it’s a bit like swimming against the tide to try to enforce it, so you may play around with one that does like Clojure.

不幸的是,JS没有提供开箱即用的功能,因此有点像赶潮流来尝试执行它,因此您可以尝试使用Clojure之类的工具

But they’re great resources to learn this paradigm in JavaScript which I recommend because the functional languages are often less popular and with ecosystems of learning materials less mature.

但是,它们是学习JavaScript范式的好资源,我建议这样做,因为功能语言通常不那么受欢迎,并且学习材料的生态系统还不成熟。

Here are few of them:

以下是其中一些:

Books:

图书:

- Mostly adequate guide to FP (in JS)

-FP的基本指南(在JS中)

Videos:

影片:

Libraries:

库:

More

更多

结论 (Conclusion)

That’s it !

而已 !

I hope you like this post and mostly, that it will be useful.

希望您喜欢这篇文章,并且希望它对您有所帮助。

Keep coding ! 😄

继续编码! 😄

翻译自: https://medium.com/dev-genius/from-imperative-to-declarative-javascript-f6bd8eec05bd

命令式 声明式

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值