有关JavaScript中条件逻辑的权威指南

by Nick Gard

尼克·加德(Nick Gard)

I am a front-end engineer and mathematician. I rely on my mathematical training daily in writing code. It’s not statistics or calculus that I use but, rather, my thorough understanding of Boolean logic. Often I have turned a complex combination of ampersands, pipes, exclamation marks, and equals signs into something simpler and much more readable. I’d like to share this knowledge, so I wrote this article. It’s long but I hope it is as beneficial to you as it has been to me. Enjoy!

我是一名前端工程师和数学家。 我每天依靠我的数学训练来编写代码。 我使用的不是统计数据或微积分,而是我对布尔逻辑的透彻理解。 通常,我将&符号,管道,感叹号和等号的复杂组合变成了更简单,更易读的内容。 我想分享这些知识,所以我写了这篇文章。 时间很长,但我希望它对您和我一样有益。 请享用!

JavaScript中的Truthy和Falsy值 (Truthy & Falsy values in JavaScript)

Before studying logical expressions, let’s understand what’s “truthy” in JavaScript. Since JavaScript is loosely typed, it coerces values into booleans in logical expressions. if statements, &&, ||, and ternary conditions all coerce values into booleans. Note that this doesn’t mean that they always return a boolean from the operation.

在研究逻辑表达式之前,让我们了解一下JavaScript中的“真实”内容。 由于JavaScript是松散类型的,因此它将逻辑表达式中的值强制转换为布尔值。 if语句&&|| 以及三元条件都将值强制转换为布尔值。 请注意 ,这并不意味着它们总是从操作中返回布尔值。

There are only six falsy values in JavaScript — false, null, undefined, NaN, 0, and "" — and everything else is truthy. This means that [] and {} are both truthy, which tend to trip people up.

只有6个falsy值在JavaScript中- falsenullundefinedNaN0"" -和其他一切都是truthy。 这意味着[]{}都是真实的,容易使人绊倒。

逻辑运算符 (The logical operators)

In formal logic, only a few operators exist: negation, conjunction, disjunction, implication, and bicondition. Each of these has a JavaScript equivalent: !, &&, ||, if (/* condition */) { /* then consequence */}, and ===, respectively. These operators create all other logical statements.

在形式逻辑中,只有少数运算符存在:否定,合取,析取,蕴涵和双条件。 这些每个都有一个JavaScript等效项: !&&||if (/* condition */) { /* then consequence */}=== 。 这些运算符创建所有其他逻辑语句。

真相表 (Truth Tables)

First, let’s look at the truth tables for each of our basic operators. A truth table tells us what the truthiness of an expression is based on the truthiness of its parts. Truth tables are important. If two expressions generate the same truth table, then those expressions are equivalent and can replace one another.

首先,让我们看一下每个基本运算符的真值表 。 真值表告诉我们表达式的真实性基于其部分的真实性。 真相表很重要。 如果两个表达式生成相同的真值表,则这些表达式是等效的,并且可以彼此替换

The Negation table is very straightforward. Negation is the only unary logical operator, acting only on a single input. This means that !A || B is not the same as !(A || B). Parentheses act like the grouping notation you’d find in mathematics.

否定表非常简单。 否定是唯一的一元逻辑运算符,仅对单个输入起作用。 这意味着!A || B !A || B!(A || B) 。 括号的作用类似于您在数学中找到的分组符号。

For instance, the first row in the Negation truth table (below) should be read like this: “if statement A is True, then the expression !A is False.”

例如,“否定真值”表(如下)的第一行应如下所示:“如果语句A为True,则表达式!A为False。”

Negating a simple statement is not difficult. The negation of “it is raining” is “it is not raining,” and the negation of JavaScript’s primitive true is, of course, false. However, negating complex statements or expressions is not so simple. What is the negation of “it is always raining” or isFoo && isBar?

否定简单的声明并不困难。 对“正在下雨”的否定是“ 下雨”,JavaScript原始的true的否定当然是false 。 但是,否定复杂的语句或表达式并不是那么简单。 “ 总是下雨”或isFoo && isBar什么isFoo && isBar

The Conjunction table shows that the expression A && B is true only if both A and B are true. This should be very familiar from writing JavaScript.

连词表显示的是,表达A && B为真仅当A和B 为真。 通过编写JavaScript,应该非常熟悉。

The Disjunction table should also be very familiar. A disjunction (logical OR statement) is true if either or both of A and B are true.

Disjunction表也​​应该非常熟悉。 如果一个两个都满足,则析取(逻辑OR语句)为true A和B中的A是正确的。

The Implication table is not as familiar. Since A implies B, A being true implies B is true. However, B can be true for reasons other than A, which is why the last two lines of the table are true. The only time implication is false is when A is true and B is false because then A doesn’t imply B.

暗示表不是很熟悉。 由于A 表示 B,所以A为真意味着B为真。 但是,由于A以外的其他原因,B可能为真,这就是表的最后两行为真的原因。 唯一暗示为假的时间是当A为true且B为false时,因为那时A并不意味着B。

While if statements are used for implications in JavaScript, not all ifstatements work this way. Usually, we use if as a flow control, not as a truthiness check where the consequence also matters in the check. Here is the archetypical implication if statement:

尽管if语句用于JavaScript中的含义,但并非所有if语句都可以这种方式工作。 通常,我们将if用作流量控制,而不是真实性检查,因为结果在检查中也很重要。 这是if语句的原型含义

function implication(A, B) {  if (A) {    return B;  } else {    /* if A is false, the implication is true */    return true;  }}

Don’t worry that this is somewhat awkward. There are easier ways to code implications. Because of this awkwardness, though, I will continue to use as the symbol for implications throughout this article.

不必担心这有点尴尬。 有更简单的方法来编码含义。 由于这种尴尬,我将继续在本文中使用来表示含义。

The Bicondition operator, sometimes called if-and-only-if (IFF), evaluates to true only if the two operands, A and B, share the same truthiness value. Because of how JavaScript handles comparisons, the use of === for logical purposes should only be used on operands cast to booleans. That is, instead of A === B, we should use !!A === !!B.

条件运算符(有时称为if-and-if-if(IFF))仅在两个操作数A和B共享相同的真实性值时计算为true。 由于JavaScript处理比较的方式,出于逻辑目的将===用途仅应用于强制转换为布尔值的操作数。 也就是说,我们应该使用!!A === !!B代替A === B

注意事项 (Caveats)

There are two big caveats to treating JavaScript code like propositional logic: short circuiting and order of operations.

将JavaScript代码视为命题逻辑有两个大警告: 短路操作顺序

Short circuiting is something that JavaScript engines do to save time. Something that will not change the output of the whole expression is not evaluated. The function doSomething() in the following examples is never called because, no matter what it returned, the outcome of the logical expression wouldn’t change:

短路是JavaScript引擎可以节省时间的方法。 不会改变整个表达式的输出的内容不会得到评估。 以下示例中的函数doSomething()永远不会被调用,因为无论返回什么,逻辑表达式的结果都不会改变:

// doSomething() is never calledfalse && doSomething();true || doSomething();

Recall that conjunctions (&&) are true only if both statements are true, and disjunctions (||) are false only if both statements are false. In each of these cases, after reading the first value, no more calculations need to be done to evaluate the logical outcome of the expressions.

回想一下, 仅当 两个语句都为true时 ,连词( && ) 为true ,而仅当 两个语句都为false时 ,析取词( || ) 才为false。 在每种情况下,在读取第一个值之后,无需执行任何其他计算来评估表达式的逻辑结果。

Because of this feature, JavaScript sometimes breaks logical commutativity. Logically A && B is equivalent to B && A, but you would break your program if you commuted window && window.mightNotExist into window.mightNotExist && window. That’s not to say that the truthiness of a commuted expression is any different, just that JavaScript may throw an error trying to parse it.

由于此功能,JavaScript有时会破坏逻辑可交换性。 从逻辑上讲, A && B等效于B && A ,但是如果将window && window.mightNotExist转换为window.mightNotExist && window ,则会破坏程序。 这并不是说换向表达式的真实性有任何不同,只是JavaScript 可能会在尝试解析它时抛出错误。

The order of operations in JavaScript caught me by surprise because I was not taught that formal logic had an order of operations, other than by grouping and left-to-right. It turns out that many programming languages consider &&to have a higher precedence than ||. This means that && is grouped (not evaluated) first, left-to-right, and then || is grouped left-to-right. This means that A || B && C is not evaluated the same way as (A || B) && C, but rather as A || (B && C).

JavaScript的操作顺序使我感到惊讶,因为除分组和从左到右之外,我没有被告知正规逻辑具有操作顺序。 事实证明,许多编程语言都认为&&优先级高于|| 。 这意味着&&首先从左到右,然后||分组(不评估)。 从左到右分组。 这意味着A || B && C A || B && C 计算相同的方式, (A || B) && C ,而是作为A || (B && C) A || (B && C)

true || false && false; // evaluates to true(true || false) && false; // evaluates to false

Fortunately, grouping, (), holds the topmost precedence in JavaScript. We can avoid surprises and ambiguity by manually associating the statements we want evaluated together into discrete expressions. This is why many code linters prohibit having both && and || within the same group.

幸运的是, 分组 ()在JavaScript中拥有最高的优先级。 通过手动将要评估的语句一起关联到离散表达式中,我们可以避免意外和歧义。 这就是为什么许多代码linter禁止同时使用&&|| 在同一组中。

计算复合真值表 (Calculating compound truth tables)

Now that the truthiness of simple statements is known, the truthiness of more complex expressions can be calculated.

既然知道了简单语句的真实性,就可以计算出更复杂的表达式的真实性。

To begin, count the number of variables in the expression and write a truth table that has 2ⁿ rows.

首先,计算表达式中的变量数,并编写一个包含2行的真值表。

Next, create a column for each of the variables and fill them with every possible combination of true/false values. I recommend filling the first half of the first column with T and the second half with F, then quartering the next column and so on until it looks like this:

接下来,为每个变量创建一列,并用真/假值的每种可能组合填充它们。 我建议用T填充第一列的前半部分,并用F填充后半部分,然后将下一列四分之一,依此类推,直到看起来像这样:

Then write the expression down and solve it in layers, from the innermost groups outward for each combination of truth values:

然后写下表达式并逐层求解,从最里面的组开始到真值的每种组合:

As stated above, expressions which produce the same truth table can be substituted for each other.

如上所述,产生相同真值表的表达式可以彼此替换。

更换规则 (Rules of replacements)

Now I’ll cover several examples of rules of replacements that I often use. No truth tables are included below, but you can construct them yourself to prove that these rules are correct.

现在,我将介绍几个我经常使用的替换规则示例。 下面没有包含真值表,但是您可以自己构造它们以证明这些规则是正确的。

双重否定 (Double negation)

Logically, A and !!A are equivalent. You can always remove a double negation or add a double negation to an expression without changing its truthiness. Adding a double-negation comes in handy when you want to negate part of a complex expression. The one caveat here is that in JavaScript !! also acts to coerce a value into a boolean, which may be an unwanted side-effect.

从逻辑上讲, A!!A是等效的。 您可以始终删除表达式的双重否定或添加否定,而无需更改其真实性。 当您要否定复杂表达式的一部分时,添加双重否定会很方便。 这里的一个警告是JavaScript !! 还可以将值强制转换为布尔值,这可能是有害的副作用。

A === !!A

A === !!A

换向 (Commutation)

Any disjunction (||), conjunction (&&), or bicondition (===) can swap the order of its parts. The following pairs are logically equivalent, but may change the program’s computation because of short-circuiting.

任何析取( || ),合取( && )或双条件( === )都可以交换其部分的顺序。 以下对在逻辑上是等效的,但由于短路可能会改变程序的计算。

(A || B) === (B || A)

(A || B) === (B || A)

(A || B) === (B || A)(A && B) === (B && A)

(A || B) === (B || A) (A && B) === (B && A)

(A || B) === (B || A)(A && B) === (B && A)(A === B) === (B === A)

(A || B) === (B || A) (A && B) === (B && A) (A === B) === (B === A)

协会 (Association)

Disjunctions and conjunctions are binary operations, meaning they only operate on two inputs. While they can be coded in longer chains — A || B || C || D — they are implicitly associated from left to right — ((A || B) || C) || D. The rule of association states that the order in which these groupings occur make no difference to the logical outcome.

析取和合取是二进制运算,这意味着它们仅在两个输入上运算。 虽然可以将它们编码成更长的链— A || B || C || D A || B || C || D A || B || C || D —它们从左到右隐式关联— ((A || B) || C) || D ((A || B) || C) || D 关联规则指出,这些分组的发生顺序对逻辑结果没有影响。

((A || B) || C) === (A || (B || C))

((A || B) || C) === (A || (B || C))

((A || B) || C) === (A || (B || C))((A && B) && C) === (A && (B && C))

((A || B) || C) === (A || (B || C)) ((A && B) && C) === (A && (B && C))

分配 (Distribution)

Association does not work across both conjunctions and disjunctions. That is, (A && (B || C)) !== ((A && B) || C). In order to disassociate B and C in the previous example, you must distribute the conjunction — (A && B) || (A && C). This process also works in reverse. If you find a compound expression with a repeated disjunction or conjunction, you can un-distribute it, akin to factoring out a common factor in an algebraic expression.

关联不适用于连接词和析取词。 也就是说, (A && (B || C)) !== ((A && B) || C) 。 为了在上一个示例中取消BC关联,您必须分发连接符- (A && B) || (A && C) (A && B) || (A && C) 。 此过程也相反。 如果您发现具有重复析取或连词的复合表达式,则可以取消分布它,就像在代数表达式中排除公因子一样。

(A && (B || C)) === ((A && B) || (A && C))

(A && (B || C)) === ((A && B) || (A && C))

(A && (B || C)) === ((A && B) || (A && C))(A || (B && C)) === ((A || B) && (A || C))

(A && (B || C)) === ((A && B) || (A && C)) (A || (B && C)) === ((A || B) && (A || C))

Another common occurrence of distribution is double-distribution (similar to FOIL in algebra):1. ((A || B) && (C || D)) === ((A || B) && C) || ((A || B) && D)2. ((A || B) && C) || ((A || B) && D) ===((A && C) || B && C)) || ((A && D) || (B && D))

分布的另一个常见出现是双分布(类似于代数中的FOIL):1。 ((A || B) && (C || D)) === ((A || B) && C) || ((A || B) && D) ((A || B) && (C || D)) === ((A || B) && C) || ((A || B) && D) 2. ((A || B) && C) || ((A || B) && D) === ((A || B) && C) || ((A || B) && D) === ((A && C) || B && C)) || ((A && D) || (B && D)) ((A && C) || B && C)) || ((A && D) || (B && D))

(A || B) && (C || D) === (A && C) || (B && C) || (A && D) || (B && D)

(A || B) && (C || D) === (A && C) || (B && C) || (A && D) || (B && D)

(A || B) && (C || D) === (A && C) || (B && C) || (A && D) || (B && D)(A && B) ||(C && D) === (A || C) && (B || C) && (A || D) && (B || D)

(A || B) && (C || D) === (A && C) || (B && C) || (A && D) || (B && D) (A || B) && (C || D) === (A && C) || (B && C) || (A && D) || (B && D) (A && B) ||(C && D) === (A || C) && (B || C) && (A || D) && (B || D)

实质意义 (Material Implication)

Implication expressions (A → B) typically get translated into code as if (A) { B } but that is not very useful if a compound expression has several implications in it. You would end up with nested if statements — a code smell. Instead, I often use the material implication rule of replacement, which says that A → B means either A is false or B is true.

隐含表达式( A → B )通常像if (A) { B }一样被翻译成代码,但是如果复合表达式中包含多个隐含含义,这将不是很有用。 您最终将获得嵌套的if语句-一种代码味道。 相反,我经常使用替换的物质蕴含规则,即A → B表示A为假或B为真。

(A → B) === (!A || B)

(A → B) === (!A || B)

重言式与矛盾 (Tautology & Contradiction)

Sometimes during the course of manipulating compound logical expressions, you’ll end up with a simple conjunction or disjunction that only involves one variable and its negation or a boolean literal. In those cases, the expression is either always true (a tautology) or always false (a contradiction) and can be replaced with the boolean literal in code.

有时,在处理复合逻辑表达式的过程中,您将得到一个简单的合取或析取,仅涉及一个变量及其取反或布尔文字。 在这些情况下,表达式要么始终为true(重言式),要么始终为false(矛盾),并且可以用代码中的布尔文字替换。

(A || !A) === true

(A || !A) === true

(A || !A) === true(A || true) === true

(A || !A) === true (A || true) === true

(A || !A) === true(A || true) === true(A && !A) === false

(A || !A) === true (A || true) === true (A && !A) === false

(A || !A) === true(A || true) === true(A && !A) === false(A && false) === false

(A || !A) === true (A || true) === true (A && !A) === false (A && false) === false

Related to these equivalencies are the disjunction and conjunction with the other boolean literal. These can be simplified to just the truthiness of the variable.

与这些等价相关的是析取和与另一个布尔文字的结合。 这些可以简化为变量的真实性。

(A || false) === A

(A || false) === A

(A || false) === A(A && true) === A

(A || false) === A (A && true) === A

换位 (Transposition)

When manipulating an implication (A → B), a common mistake people make is to assume that negating the first part, A, implies the second part, B, is also negated — !A → !B. This is called the converse of the implication and it is not necessarily true. That is, having the original implication does not tell us if the converse is true because A is not a necessary condition of B. (If the converse is also true — for independent reasons — then A and B are biconditional.)

当处理一个蕴涵( A → B )时,人们常犯的一个错误是假定否定第一部分A意味着第二部分B也被否定了- !A → !B 。 这被称为蕴涵的反面不一定是正确的 。 也就是说,因为A不是B必要条件,所以具有原始含义并不能告诉我们相反是否成立。 (如果相反也是如此(出于独立原因),则AB是双条件的。)

What we can know from the original implication, though, is that the contrapositive is true. Since B is a necessary condition for A (recall from the truth table for implication that if B is true, A must also be true), we can claim that !B → !A.

但是,从最初的含义中我们可以知道, 对立是正确的。 由于B A的必要条件(请从真值表中调用,以暗示如果B为true,则A也必须为true),因此我们可以声明!B → !A

(A → B) === (!B → !A)

(A → B) === (!B → !A)

材料等效 (Material Equivalence)

The name biconditional comes from the fact that it represents two conditional (implication) statements: A === B means that A → B and B → A. The truth values of A and B are locked into each other. This gives us the first material equivalence rule:

名称双条件来源于事实,即它表示两个条件(含义)语句: A === B装置,其A → B B → A AB的真值相互锁定。 这给了我们第一个物质等效规则:

(A === B) === ((A → B) && (B → A))

(A === B) === ((A → B) && (B → A))

Using material implication, double-distribution, contradiction, and commutation, we can manipulate this new expression into something easier to code:1. ((A → B) && (B → A)) === ((!A || B) && (!B || A))2. ((!A || B) && (!B || A)) === ((!A && !B) || (B && !B)) || ((!A && A) || (B && A))3. ((!A && !B) || (B && !B)) || ((!A && A) || (B && A)) === ((!A && !B) || (B && A))4. ((!A && !B) || (B && A)) === ((A && B) || (!A && !B))

使用实质含义,双重分布,矛盾和交换,我们可以将此新表达式操纵为更易于编码的内容:1。 ((A → B) && (B → A)) === ((!A || B) && (!B || A)) 2. ((!A || B) && (!B || A)) === ((!A && !B) || (B && !B)) || ((!A && A) || (B && A)) ((!A && !B) || (B && !B)) || ((!A && A) || (B && A)) 3. ((!A && !B) || (B && !B)) || ((!A && A) || (B && A)) === ((!A && !B) || (B && !B)) || ((!A && A) || (B && A)) === ((!A && !B) || (B && A)) 4. ((!A && !B) || (B && A)) === ((A && B) || (!A && !B))

(A === B) === ((A && B) || (!A && !B))

(A === B) === ((A && B) || (!A && !B))

外销 (Exportation)

Nested if statements, especially if there are no else parts, are a code smell. A simple nested if statement can be reduced into a single statement where the conditional is a conjunction of the two previous conditions:

嵌套的if语句(特别是如果没有else部分的话)是代码的味道。 一个简单的嵌套if语句可以简化为单个语句,其中条件是前两个条件的结合:

if (A) {  if (B) {    C  }}// is equivalent toif (A && B) {  C}

(A → (B → C)) === ((A && B) → C)

(A → (B → C)) === ((A && B) → C)

德摩根定律 (DeMorgan’s Laws)

DeMorgan’s Laws are essential to working with logical statements. They tell how to distribute a negation across a conjunction or disjunction. Consider the expression !(A || B). DeMorgan’s Laws say that when negating a disjunction or conjunction, negate each statement and change the && to ||or vice versa. Thus !(A || B) is the same as !A && !B. Similarly, !(A && B)is equivalent to !A || !B.

德摩根定律对于处理逻辑陈述至关重要。 他们告诉如何将否定分布在一个合取或析取之间。 考虑表达式!(A || B) 。 德摩根定律说,当否定析取或合词时,请否定每个陈述并将&&更改为|| 或相反亦然。 因此!(A || B)!A && !B 。 同样, !(A && B)等同于!A || !B !A || !B

!(A || B) === !A && !B

!(A || B) === !A && !B

!(A || B) === !A && !B!(A && B) === !A || !B

!(A || B) === !A && !B !(A && B) === !A || !B !(A && B) === !A || !B

三元(If-Then-Else) (Ternary (If-Then-Else))

Ternary statements (A ? B : C) occur regularly in programming, but they’re not quite implications. The translation from a ternary to formal logic is actually a conjunction of two implications, A → B and !A → C, which we can write as: (!A || B) && (A || C), using material implication.

三元语句( A ? B : C )在编程中经常出现,但意义不大。 从三元逻辑到形式逻辑的转换实际上是两个含义的结合,即A → B!A → C ,我们可以使用物质含义将它们写成:( (!A || B) && (A || C)

(A ? B : C) === (!A || B) && (A || C)

(A ? B : C) === (!A || B) && (A || C)

异或(异或) (XOR (Exclusive Or))

Exclusive Or, often abbreviated xor, means, “one or the other, but not both.” This differs from the normal or operator only in that both values cannot be true. This is often what we mean when we use “or” in plain English. JavaScript doesn’t have a native xor operator, so how would we represent this? 1. “A or B, but not both A and B”2. (A || B) && !(A && B) direct translation3. (A || B) && (!A || !B) DeMorgan’s Laws4. (!A || !B) && (A || B) commutativity5. A ? !B : B if-then-else definition

或,通常缩写为xor ,意思是“一个或另一个,但不能同时存在。” 这与正常值运算符的不同之处仅在于两个值都不能为真。 这就是我们用普通英语使用“或”时的意思。 JavaScript没有原生的xor运算符,那么我们将如何表示呢? 1.“ A或B,但不同时包括A和B” 2。 (A || B) && !(A && B) 直接翻译 3. (A || B) && (!A || !B) 德摩根定律 4. (!A || !B) && (A || B) 可交换性 5. A ? !B : B A ? !B : B if-then-else定义

A ? !B : B is exclusive or (xor) in JavaScript

A ? !B : B A ? !B : B在JavaScript中是互斥或(xor)

Alternatively,1. “A or B, but not both A and B”2. (A || B) && !(A && B) direct translation3. (A || B) && (!A || !B) DeMorgan’s Laws4. (A && !A) || (A && !B) || (B && !A) || (B && !B) double-distribution5. (A && !B) || (B && !A) contradiction replacement6. A === !B or A !== B material equivalence

或者,1。 “ A或B,但不能同时包含A和B” 2。 (A || B) && !(A && B) 直接翻译 3. (A || B) && (!A || !B) 德摩根定律 4. (A && !A) || (A && !B) || (B && !A) || (B && !B) (A && !A) || (A && !B) || (B && !A) || (B && !B) (A && !A) || (A && !B) || (B && !A) || (B && !B) 双分配 5. (A && !B) || (B && !A) (A && !B) || (B && !A) 矛盾替换 6. A === !BA !== B 材料等效

A === !B or A !== B is xor in JavaScript

A === !B A !== B在JavaScript中是xor

设定逻辑 (Set Logic)

So far we have been looking at statements about expressions involving two (or a few) values, but now we will turn our attention to sets of values. Much like how logical operators in compound expressions preserve truthiness in predictable ways, predicate functions on sets preserve truthiness in predictable ways.

到目前为止,我们一直在研究关于涉及两个(或几个)值的表达式的语句,但是现在我们将注意力转向值集。 就像复合表达式中的逻辑运算符如何以可预测的方式保持真实性一样,集合上的谓词函数以可预测的方式保持真实性。

A predicate function is a function whose input is a value from a set and whose output is a boolean. For the following code examples, I will use an array of numbers for a set and two predicate functions:isOdd = n => n % 2 !== 0; and isEven = n => n % 2 === 0;.

谓词函数是一种函数,其输入是来自集合的值,而其输出是布尔值。 对于以下代码示例,我将对一组数字和两个谓词函数使用数字数组: isOdd = n => n % 2 !== 0; nd isEven = n => n % 2 === 0;。

通用声明 (Universal Statements)

A universal statement is one that applies to all elements in a set, meaning its predicate function returns true for every element. If the predicate returns false for any one (or more) element, then the universal statement is false. Array.prototype.every takes a predicate function and returns true only if every element of the array returns true for the predicate. It also terminates early (with false) if the predicate returns false, not running the predicate over any more elements of the array, so in practice avoid side-effects in predicates.

通用语句是适用于集合中所有元素的语句,这意味着其谓词函数对每个元素都返回true。 如果谓词对于任何一个(或多个)元素返回false,则通用语句为false。 Array.prototype.every具有谓词功能,并且仅当数组的每个元素对该谓词返回true才返回true。 如果该谓词返回false,它也将尽早终止(以false结束),而不是使谓词在数组的任何其他元素上运行,因此在实践中避免谓词产生副作用

As an example, consider the array [2, 4, 6, 8], and the universal statement, “every element of the array is even.” Using isEven and JavaScript’s built-in universal function, we can run [2, 4, 6, 8].every(isEven) and find that this is true.

例如,考虑数组[2, 4, 6, 8]和通用语句“数组的每个元素都是偶数”。 使用isEven和JavaScript的内置通用函数,我们可以运行[2, 4, 6, 8].every(isEven)并发现这是true

Array.prototype.every is JavaScript’s Universal Statement

Array.prototype.every是JavaScript的通用声明

存在陈述 (Existential Statements)

An existential statement makes a specific claim about a set: at least one element in the set returns true for the predicate function. If the predicate returns false for every element in the set, then the existential statement is false.

存在性语句对某个集合有特定的主张:集合中的至少一个元素对谓词函数返回true。 如果谓词对集合中的每个元素都返回false,则存在性语句为false。

JavaScript also supplies a built-in existential statement: Array.prototype.some. Similar to every, some will return early (with true) if an element satisfies its predicate. As an example, [1, 3, 5].some(isOdd) will only run one iteration of the predicate isOdd (consuming 1 and returning true) and return true. [1, 3, 5].some(isEven) will return false.

JavaScript还提供了一个内置的存在性声明: Array.prototype.some 。 与every相似,如果元素满足其谓词,则some元素会提早返回(真)。 例如, [1, 3, 5].some(isOdd)将仅运行谓词isOdd一次迭代(消耗1并返回true )并返回true[1, 3, 5].some(isEven)将返回false

Array.prototype.some is JavaScript’s Existential Statement

Array.prototype.some是JavaScript的存在语句

普遍含义 (Universal Implication)

Once you have checked a universal statement against a set, say nums.every(isOdd), it is tempting to think that you can grab an element from the set that satisfies the predicate. However, there is one catch: in Boolean logic, a true universal statement does not imply that the set is non-empty. Universal statements about empty sets are always true, so if you wish to grab an element from a set satisfying some condition, use an existential check instead. To prove this, run [].every(() => false). It will be true.

一旦针对集合检查了通用语句nums.every(isOdd)例如nums.every(isOdd) ,就很容易想到您可以从满足谓词的集合中获取元素。 但是,有一个陷阱:在布尔逻辑中,真正的通用语句并不意味着该集合是非空的。 关于空集的通用语句始终为true ,因此,如果您希望从满足某些条件的集中获取元素,请改用存在性检查。 为了证明这一点,请运行[].every(() => fal se)。 会的。

Universal statements about empty sets are always true.

关于空集的通用语句始终为true

否定通用和现有语句 (Negating Universal and Existential Statements)

Negating these statements can be surprising. The negation of a universal statement, say nums.every(isOdd), is not nums.every(isEven), but rather nums.some(isEven). This is an existential statement with the predicate negated. Similarly, the negation of an existential statement is a universal statement with the predicate negated.

否定这些声明可能会令人惊讶。 通用语句的否定,例如nums.every(isOdd) ,不是nums.every(isEven) ,而是nums.some(isEven) 。 这是谓词被否定的存在性陈述。 类似地,存在性陈述的否定是谓词被否定的通用陈述。

!arr.every(el => fn(el)) === arr.some(el => !fn(el))

!arr.every(el => fn(el)) === arr.some(el => !fn(el))

!arr.every(el => fn(el)) === arr.some(el => !fn(el))!arr.some(el => fn(el)) === arr.every(el => !fn(el))

!arr.every(el => fn(el)) === arr.some(el => !fn(el)) !arr.some(el => fn(el)) === arr.every(el =& gt;!fn(el))

设置相交 (Set Intersections)

Two sets can only be related to each other in a few ways, with regards to their elements. These relationships are easily diagrammed with Venn Diagrams, and can (mostly) be determined in code using combinations of universal and existential statements.

就其元素而言,两组只能以几种方式彼此关联。 这些关系很容易用维恩图(Venn Diagrams)来表示,并且(大多数)可以使用通用和存在性语句的组合在代码中确定。

Two sets can each share some but not all of their elements, like a typical conjoined Venn Diagram:

两组可以共享一些但不是全部元素,例如典型的联合维恩图:

A.some(el => B.includes(el)) && A.some(el => !B.includes(el)) && B.some(el => !A.includes(el)) describes a conjoined pair of sets

A.some(el => B.includes(el)) && A.some(el => !B.includes(el)) && B.some(el => !A.incl udes(el))描述了一个A.some(el => B.includes(el)) && A.some(el => !B.includes(el)) && B.some(el => !A.incl对套

One set can contain all of the other set’s elements, but have elements not shared by the second set. This is a subset relationship, denoted as Subset ⊆ Superset.

一个集合可以包含其他集合的所有元素,但具有第二个集合不共享的元素。 这是一个子集关系,表示为Subset ⊆ Superset

B.every(el => A.includes(el)) describes the subset relationship B ⊆ A

B.every(el => A.includes(e l))描述子集关系B⊆A

The two sets can share no elements. These are disjoint sets.

两组不能共享任何元素。 这些是不相交的集合。

A.every(el => !B.includes(el)) describes a disjoint pair of sets

A.every(el => !B.includes(e l))描述一组不相交的集合

Lastly, the two sets can share every element. That is, they are subsets of each other. These sets are equal. In formal logic, we would write A ⊆ B && B ⊆ A ⟷ A === B, but in JavaScript, there are some complications with this. In JavaScript, an Array is an ordered set and may contain duplicate values, so we cannot assume that the bidirectional subset code B.every(el => A.includes(el)) && A.every(el => B.includes(el)) implies the arrays A and B are equal. If A and B are Sets (meaning they were created with new Set()), then their values are unique and we can do the bidirectional subset check to see if A === B.

最后,这两个集合可以共享每个元素。 也就是说,它们是彼此的子集。 这些集合是相等的 。 在形式逻辑中,我们将写成A ⊆ B && B ⊆ A ⟷ A === B ,但是在JavaScript中,这样做有些复杂。 在JavaScript中, Array有序集合,并且可能包含重复值,因此我们不能假定双向子集代码B.every(el => A.includes(el)) && A.every(el => B.include s致发光(EL))意味着一个r射线A和B是EQUA l 。 如果A和B是集合(意味着它们是with new Set()创建的),则它们的值是唯一的, ee if A === B,我们可以对s ee if A进行双向子集检查。

(A === B) === (Array.from(A).every(el => Array.from(B).includes(el)) && Array.from(B).every(el => Array.from(A).includes(el)), given that A and Bare constructed using new Set()

(A === B) === (Array.from(A).every(el => Array.from(B).includes(el)) && Array.from(B).every(el => Array.from(A).include s(el)),给定 A和Bare using new Set()构造的

逻辑翻译成英文 (Translating Logic to English)

This section is probably the most useful in the article. Here, now that you know the logical operators, their truth tables, and rules of replacement, you can learn how to translate an English phrase into code and simplify it. In learning this translation skill, you will also be able to read code better, storing complex logic in simple phrases in your mind.

本节可能是本文中最有用的。 在这里,您已经了解了逻辑运算符,它们的真值表和替换规则,您可以学习如何将英语短语翻译为代码并进行简化 。 通过学习这种翻译技巧,您还可以更好地阅读代码,将复杂的逻辑以简单的短语存储在您的脑海中。

Below is a table of logical code (left) and their English equivalents (right) that was heavily borrowed from the excellent book, Essentials of Logic.

下表是一本从优秀书籍Logic Essentials大量借用的逻辑代码(左)及其等效的英语(右)表

Below, I will go through some real-world examples from my own work where I interpret from English to code, and vice-versa, and simplify code with the rules of replacement.

下面,我将介绍一些我自己的工作中的真实示例,在这些示例中,我将英语解释为代码,反之亦然,并使用替换规则简化代码。

例子1 (Example 1)

Recently, to satisfy the EU’s GDPR requirements, I had to create a modal that showed my company’s cookie policy and allowed the user to set their preferences. To make this as unobtrusive as possible, we had the following requirements (in order of precedence):

最近,为了满足欧盟的GDPR要求,我必须创建一个模式来显示我公司的Cookie政策,并允许用户设置自己的偏好。 为了使它尽可能不引人注目,我们有以下要求(按优先顺序排列):

  1. If the user wasn’t in the EU, never show the GDPR preferences modal.

    如果用户不在欧盟,请不要显示GDPR偏好模式。

  2. 2. If the app programmatically needs to show the modal (if a user action requires more permission than currently allowed), show the modal.

    2.如果应用程序以编程方式需要显示模式(如果用户操作需要的权限超出当前允许的权限),请显示模式。
  3. If the user is allowed to have the less-obtrusive GDPR banner, do not show the modal.

    如果允许用户使用不太吸引人的GDPR 标语 ,则不要显示模式。

  4. If the user has not already set their preferences (ironically saved in a cookie), show the modal.

    如果用户没有设置自己的喜好(讽刺的是保存在cookie中),显示模式。

I started off with a series of if statements modeled directly after these requirements:

我首先if这些要求直接建模了一系列if语句:

const isGdprPreferencesModalOpen = ({  shouldModalBeOpen,  hasCookie,  hasGdprBanner,  needsPermissions}) => {  if (!needsPermissions) {    return false;  }  if (shouldModalBeOpen) {    return true;  }  if (hasGdprBanner) {    return false;  }  if (!hasCookie) {    return true;  }  return false;}

To be clear, the above code works, but returning boolean literals is a code smell. So I went through the following steps:

需要明确的是,上面的代码有效,但是返回布尔文字是一种代码气味 。 因此,我经历了以下步骤:

/* change to a single return, if-else-if structure */let result;if (!needsPermissions) {  result = false;} else if (shouldBeOpen) {  result = true;} else if (hasBanner) {  result = false;} else if (!hasCookie) {  result = true} else {  result = false;}return result;
/* use the definition of ternary to convert to a single return */return !needsPermissions ? false : (shouldBeOpen ? true : (hasBanner ? false : (!hasCookie ? true : false)))
/* convert from ternaries to conjunctions of disjunctions */return (!!needsPermissions || false) && (!needsPermissions || ((!shouldBeOpen || true) && (shouldBeOpen || ((!hasBanner || false) && (hasBanner || !hasCookie))))
/* simplify double-negations and conjunctions/disjunctions with boolean literals */return needsPermissions && (!needsPermissions || ((!shouldBeOpen || true) && (shouldBeOpen || (!hasBanner && (hasBanner || !hasCookie))))
/* DeMorgan's Laws */return needsPermissions && (!needsPermissions || ((!shouldBeOpen || true) && (shouldBeOpen || ((!hasBanner && hasBanner) || (hasBanner && !hasCookie))))
/* eliminate tautologies and contradictions, simplify */return needsPermissions && (!needsPermissions || (shouldBeOpen || (hasBanner && !hasCookie)))
/* DeMorgan's Laws */return (needsPermissions && !needsPermissions) || (needsPermissions && (shouldBeOpen || (hasBanner && !hasCookie)))
/* eliminate contradiction, simplify */return needsPermissions && (shouldBeOpen || (hasBanner && !hasCookie))

I ended up with something that I think is more elegant and still readable:

我最终得到了一些我认为更优雅并且仍然可读的东西:

const isGdprPreferencesModalOpen = ({  needsPermissions,  shouldBeOpen,  hasBanner,  hasCookie,}) => (  needsPermissions && (shouldBeOpen || (!hasBanner && !hasCookie)));
例子2 (Example 2)

I found the following code (written by a coworker) while updating a component. Again, I felt the urge to eliminate the boolean literal returns, so I refactored it.

我在更新组件时发现了以下代码(由同事编写)。 同样,我感到有消除布尔文字返回的冲动,因此我对其进行了重构。

const isButtonDisabled = (isRequestInFlight, state) => {  if (isRequestInFlight) {    return true;  }  if (enabledStates.includes(state)) {    return false;  }  return true;};

Sometimes I do the following steps in my head or on scratch paper, but most often, I write each next step in the code and then delete the previous step.

有时,我会在脑海中或在草稿纸上执行以下步骤,但是最常见的是,我在代码中编写每个下一步,然后删除上一步。

// convert to if-else-if structurelet result;if (isRequestInFlight) {  result = true;} else if (enabledStates.includes(state)) {  result = false;} else {  result = true;}return result;
// convert to ternaryreturn isRequestInFlight  ? true  : enabledStates.includes(state)    ? false    : true;
/* convert from ternary to conjunction of disjunctions */return (!isRequestInFlight || true) && (isRequestInFlight || ((!enabledStates.includes(state) || false) && (enabledStates.includes(state) || true))
/* remove tautologies and contradictions, simplify */return isRequestInFlight || !enabledStates.includes(state)

Then I end up with:

然后我最终得到:

const isButtonDisabled = (isRequestInFlight, state) => (  isRequestInFlight || !enabledStates.includes(state));

In this example, I didn’t start with English phrases and I never bothered to interpret the code to English while doing the manipulations, but now, at the end, I can easily translate this: “the button is disabled if either the request is in flight or the state is not in the set of enabled states.” That makes sense. If you ever translate your work back to English and it doesn’t make sense, re-check your work. This happens to me often.

在此示例中,我不是从英语短语开始,并且在进行操作时也从未费心将代码解释为英语,但是现在,最后,我可以轻松地将其翻译为:“如果其中一个请求是处于飞行中或该状态不在启用状态集合中。” 这就说得通了。 如果您曾经将您的作品翻译回英文, 并且没有意义,请重新检查您的作品。 这经常发生在我身上。

例子3 (Example 3)

While writing an A/B testing framework for my company, we had two master lists of Enabled and Disabled experiments and we wanted to check that every experiment (each a separate file in a folder) was recorded in one or the other list but not both. This means the enabled and disabled sets are disjointed and the set of all experiments is a subset of the conjunction of the two sets of experiments. The reason the set of all experiments must be a subset of the combination of the two lists is that there should not be a single experiment that exists outside the two lists.

在为我的公司编写A / B测试框架时,我们有两个已启用和已禁用实验的主列表,我们想检查每个实验(文件夹中一个单独的文件)是否都记录在一个或另一个列表中, 但没有两个 。 这意味着启用和禁用的集合是不相交的,并且所有实验的集合是这两组实验的结合的子集。 该组中的所有实验必须在这两个列表组合的一个子集的原因是,不应该有存在两个列表一个单一的实验。

const isDisjoint = !enabled.some(el => disabled.includes(el)) &&   !disabled.some(el => enabled.includes(el));const isSubset = allExperiments.every(  el => enabled.concat(disabled).includes(el));assert(isDisjoint && isSubset);

结论 (Conclusion)

Hopefully this has all been helpful. Not only are the skills of translating between English and code useful, but having the terminology to discuss different relationships (like conjunctions and implications) and the tools to evaluate them (truth tables) is handy.

希望这对您有所帮助。 不仅在英语和代码之间进行翻译的技能很有用,而且拥有讨论不同关系(例如合词和含义)的术语以及评估它们的工具(真值表)也很方便。

翻译自: https://www.freecodecamp.org/news/a-definitive-guide-to-conditional-logic-in-javascript-23fa234d2ca3/

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值