⚡️ warning, high energy ahead:

In this article, be on the lookout for the following emoji packs:

  • White bookmark: 🔖, for level 1 title;
  • The orange quadrilateral: 🔶, for secondary headings;
  • The little blue quadrilateral: 🔹, representing the level 3 title;
  • Yellow lightning: ⚡️, for emphasis;

By reading this article, you can learn how to implement the comparison logic of relational operators in javascript. Take < for example.

⚡️ This article is based on” Draft ECMA-262 / October 15, 2020ECMAScript® 2021 Language Specification”, namely ES2021 Specification, according to their own understanding of the record. You are welcome to point out any misunderstanding.

🔖 background

If you are not interested in the background of the question, you can just skip it.

Today I want to write a bubble sort in JS. Use Node.js’s readline to enter a comma-separated number from the command line. Convert it to an array, sort it from largest to smallest, and print it.

I typed: 11,8,7,6,15

The output is: 8,7,6,15,11

Obviously not, I start the debugger and see that the readline input to the callback is a string: ‘11,8,7,6,15’.

At this point, it’s obvious that the algorithm is comparing strings instead of numbers, which is not what I intended when I wrote the code. The problem of casting an array of code strings to an array of numbers is solved. But be curious. I’ll follow the code and see what’s going on:

  1. It into the array, then ‘11,8,7,6,15’. The split (‘, ‘) – > [‘ 11 ‘, ‘8’, ‘7’, ‘6’, ’15’].
  2. According to the result ‘6’ > ’15’, so ’15’ < ‘6’.
  3. So the question is why ’15’ < ‘6’ in JS.

ECMAScript® 2021 Language Specification (ECMAScript® 2021 Language Specification

Hence, this document.

The text begins.

🔖 i. Definition of < operation

Tc39.es/ECMA262 /# SE

You can clearly see when the < operation is performed. After the two operands were evaluated, lVAL and RVAL were obtained, and then the Abstract Relational Comparison algorithm was used for Comparison. ⚡️ pay attention to the last line “7. Returns false if undefined, or true otherwise.

The following is a simple interpretation of the Abstract Relational Comparison algorithm.

🔖 2. A simple interpretation of the Abstract Relational Comparison algorithm used in Relational calculation in the specification

First, look at the body process.

Tc39.es/ECMA262 /# SE :

Read it in order:

  1. Call ToPrimitive(x, number), and return the operand if it is not of type Object. Otherwise, an exception is thrown if the conversion fails. For Object types, this step involves calling the @@Toprimitive method, or the OrdinaryToPrimitive type, which is resolved later. This does not affect our understanding of the comparison algorithm.

  2. If px and py are strings.

    1. If py is a prefix of px. ⚡️ algorithm terminated, return false
    2. If px is a prefix for py. ⚡️ algorithm terminated, return true
    3. Find the encoding value (integer) of the first different character px and py. The size relationship is calculated by comparing the encoding of the character. ⚡️ algorithm terminates, returning true or false.
  3. If one operand is BigInt, the other operand is String. The String operand is converted to BigInt using the StringToBigInt method.

    1. If the converted operand is NaN. ⚡️ algorithm terminates and returns undefind
    2. Use BigInt::lessThan(nx, py) to compare the size of two BigInt operands. Depending on the result: ⚡️ algorithm terminates, returning true or false
  4. Calls the ToNumeric method, which returns if the operand is of type BigInt. The ToNumber method is called if the operand is not BigInt. Convert according to the following rules:

    [ECMAScript® 2021 Language Specification] TC39.es/ECMA262 /# SE

    Note: In the above table rules:

    • Undefined converts to NaN,
    • Null converts to 0,
    • Boolean converts to 0 or 1,
    • Symbol throws an exception.
    • Number returns directly.
    • For strings, this is more complicated and can be interpreted as a call to parseFloat() to convert to Number.
    • ⚡️ the BigInt type conversion will not occur, because the BigInt type is determined at the previous layer.
    • ⚡️ Because in the first step of the algorithm, we have converted the operand of type Object to a non-object type. There is also no need to consider Object conversions.
  5. After the above conversion and judgment, there are only the following three cases of the final operand combination:

    • BigInt and BigInt can be directly compared
    • Number, Number, Number
    • BigInt and Number, convert Number to BigInt for comparison

    Note that when one of the operands is NaN(NaN is of type Number), ⚡️ terminates and returns undefind, while the other cases are compared mathematically (including infinity and negative infinity, of course). For example, as long as px is negative infinity, or py is positive infinity, it must return true) ⚡️ the algorithm terminates, returning true or false

At this point, the Abstract Relational Comparison algorithm is resolved.

Summary:

The Abstract Relational Comparison algorithm does the following:

  1. If both operands are strings, the String comparison rules apply.
  2. One of them is String and the other is BigInt. Then convert String to BigInt for comparison.
  3. Convert all types except BigInt to Number. The operands can only be BigInt and Number, and the size can be compared according to the mathematical logic.
  4. The algorithm will only return undefined,false,true. (⚡️ corresponds to the < runtime described in section 1, where the < operation only yields false and true)

To sum it up briefly:

⚡️ Unless both operands are strings, the operands are implicitly converted to “numeric type” for comparison. (Number refers to Number and BigInt,Number also includes NaN,Infinity, etc.)

For example, 🌰 : String vs. Number

"A" < 1 1. "a" is implicitly converted to NaN. 2. According to the Abstract Relational Comparison algorithm, undefined should be returned as long as any operand is NaN. 3. Finally, end the call of the Abstract Relational Comparison algorithm and return undefined. Back to the < runtime semantics, the result is false. So "a" < 1 prints false.Copy the code

Based on the content of the first and second sections, I can clearly analyze the problems I encountered in the background of the problem. Let’s re-analyze it:

11,8,7,6,15 input character converted to an array of strings – > [‘ 11 ‘, ‘8’, ‘7’, ‘6’, ’15’]

[“8″,”7″,”6″,”15″,”11”]

“11”< “15” <” 6″ <“7″ <” 8″ “11”< “15” <” 6″ <“7″ <” 8″

⚡️ After reading this, I believe you all have the same doubts as me. Since the Abstract Relational Comparison algorithm can accept Object types for Comparison, how can Object types be compared?

🔖 3. Interpretation of ToPrimitive algorithm used in Abstract Relational Comparison algorithm.

Through the interpretation of the second section, we know that in JS, object types can also carry out relational operations. Let’s take ToPrimitive as an entry point to see how objects are handled when relational operations are performed.

[ECMAScript® 2021 Language Specification] TC39.es/ECMA262 /# SE

You can see the ToPrimitive algorithm, which takes an input parameter and an optional preferredType. If input is not of type Object, return itself. If the input is of type Object, follow these steps.

  1. Get the @@toprimitive method. (@@toprimitive refers to the method [symbol. toPrimitive], for example, the Date object, which is defined in Symbol objects. Objects created by literals generally don’t have this method.

  2. If there is an @@toprimitive method. The preferredType is evaluated and assigned to the hint. Combine hint and input to call the @@toprimitive method. Returns the result, if the result is not of type Object. Otherwise, an exception is thrown.

  3. If there is no @@toprimitive method. The preferredType is evaluated and assigned to the hint. With hint and input, call the OrdinaryToPrimitive method (more on that below). Get the result and return.

And we can find that:

  • When no preferredType is provided and no @@toprimitive method is available, the preferredType defaults to “number”, affecting the call to the OrdinaryToPrimitive method.
  • We can provide @@toprimitive method override, default behavior. The Date object, for example, provides the @@toprimitive method.

The OrdinaryToPrimitive method used in 3 is as follows:

[ECMAScript® 2021 Language Specification] TC39.es/ECMA262 /# SE

As you can see, this ordinary topritive method is also relatively simple.

  1. The first O passed in must be of type Obejct.

  2. Hint must be “string” or “number”.

  3. Determine the order in which toString and valueOf are called next. If hint is “string”, toString,valueOf. If hint is “number”, toString,valueOf

  4. Call the two methods presented in 3 in the order specified in 3. If one of the methods yields a result that is not of type Object, terminate the OrdinaryToPrimitive method and return the result.

  5. After 1-4 with no result, an exception is thrown.

⚡️ after looking at these two algorithms, I believe we all know that comparing objects is actually the same routine by implicitly converting objects to non-object types for comparison.

Take 🌰 for example:

1. The first step in the Abstract Relational Comparison algorithm was to perform implicit conversion on the operands of the object type [1,2,3], that is, to call the ToPrimitive method. The ToPrimitive method is called in the form of ToPrimitive(x, number). 2. Because [1,2,3] does not define @@toprimitive methods. So call the OrdinaryToPrimitive method. And because the preferredType passed in as 1 is "number". So the OrdinaryToPrimitive method is called as OrdinaryToPrimitive(input,number). 3. Because in OrdinaryToPrimitive, hint is number, the toString method is called first. Returns a string of "1,2,3", non-object, and returns this result. (if the toString method returns an object, valueOf,[1,2,3] is then called. ValueOf returns "itself".) By 3, the object has been implicitly cast to a non-object type. Then go back to the D in step 4 of the Abstract Relational Comparison algorithm in Section 2 and convert the non-string type to Number. 5. The operand on the left is successfully converted from the object type to Number: NaN < 1 6. According to the Abstract Relational Comparison algorithm in Section 2, undefined is returned whenever any operands are NaN. 7. Finally, call the Abstract Relational Comparison algorithm and return undefined. Back to the < runtime semantics, the result is false. Therefore, [1,2,3] < 1 prints false.Copy the code

🔖 summary

In THE JS language specification, when the relational operation, it is possible to compare the two operands by implicit conversion according to certain rules.

The default is valueOf for an object type, and the toString method converts it to a non-object type. The call order depends on the second hint passed in when the implicit conversion OrdinaryToPrimitive method is called.

Finally, we can define @@toprimitive methods in the object (@@toprimitive refers to the [symbol.toprimitive] method) to control the result of the implicit conversion. The @@toprimitive property is defined in the Date object.

🔖 Resources

  • ECMAScript® 2021 Language Specification, TC39.es/ECMA262 /
  • How does javascript do a relational comparison? , transang. Me/how does – ja…
  • ECMAScript7 ToPrimitive abstract operation of norms, segmentfault.com/a/119000001…