
Book "{You Don't Know JS} Types and Grammar Constructions"

Like other books in the “You Don't Know JS” series , it shows non-trivial aspects of the language that JavaScript programmers prefer to stay away from (or assume that they don't exist). Armed with this knowledge, you will achieve true JavaScript mastery.
Excerpt. Equality is strict and non-strict.
Non-strict equality is checked by the == operator, and strict equality by the === operator. Both operators are used to compare two values for “equality”, but the choice of form (strict / non-strict) leads to very important differences in behavior, especially in how the decision is made on equality.
There is a common misconception about these two operators: "== checks for equality of value, and === checks for equality of both values and types." Sounds reasonable,
but inaccurate. Countless reputable JavaScript books and blogs say just that, but unfortunately they are all wrong.
The correct description is: "== allows type conversion when checking equality, and === prohibits type conversion."
Equality Verification Performance
Stop and think about how the first (inaccurate) explanation differs from the second (exact) one.
In the first explanation, it seems obvious that the === operator does more work than == because it also needs to check the type.
In the second explanation, the == operator does more work, because with different types it has to go through type conversion.
Do not fall into the trap that many fall into. Do not think that this will somehow affect the speed of the program, and == will be any significantly slower ===. Although the conversion takes some time, it takes a matter of microseconds (yes, millionths of a second).
If you compare two values of the same type, == and === use the same algorithm, so if you do not take into account small differences in the implementation of the engine, they must do
the same job.
If you are comparing two values of different types, performance is not an important factor. You have to ask yourself something else: if I am comparing two values, do I want type conversion to happen or not?
If you need a conversion, use non-strict equality ==, and if the conversion is undesirable, use strict equality ===.
Both operators, == and ===, check the types of their operands. The difference is how they respond to type mismatch.
Abstract equality check
The behavior of the == operator is defined in section 11.9.3 of the ES5 specification (“Abstract Equality Checker Algorithm”). Here is a detailed but simple algorithm, with an explicit listing of all possible combinations of types and type conversion methods (if necessary) that should be applied in each combination.
When someone condemns the (implicit) type conversion as being too complex and containing too many defects for useful practical use, he condemns the rules of the "abstract equality check". It is usually said that this mechanism is too complicated and unnatural for practical study and use, and that it creates errors in JS programs rather than simplifies code reading.
I believe that this is an erroneous assumption - you readers are competent developers who write algorithms, that is, code (and also read and understand it), all day long. For this reason, I will try to explain the "abstract equality test" in simple words. However, I also recommend reading section 11.9.3 of the ES5 specification. I think it will surprise you how logical everything is there.
In fact, the first section (11.9.3.1) states that if two compared values are of the same type, they are compared in a simple and natural way. For example, 42 is only 42, and the string “abc” is only “abc”.
A few minor exceptions to keep in mind:
- The value of NaN is never equal to itself (see chapter 2).
- ???? + 0 and -0 are equal to each other (see chapter 2).
The last section in section 11.9.3.1 is devoted to a rigorous test of == equality with objects (including functions and arrays). Two such values are equal only if both refer to exactly the same value . No type conversion is performed.
A strict equality check === is defined identically to 11.9.3.1, including the provision for two object values. This fact is very little known, but == and === behave completely identical when comparing two objects!
The rest of the algorithm in 11.9.3 indicates that the loose equality == can be used to compare two different types of values, one or both of which will require an
implicit conversion. As a result of the conversion, the designations are reduced to one type, after which they can be directly compared for equality by the simple identity of the
values.
The operation of a weak check of inequality! = Is determined exactly as one would expect; in fact, the operation == is fully implemented, followed by the calculation of the
negation of the result. The same applies to the operation of strictly checking the inequality! ==.
Comparison: strings and numbers
To demonstrate the conversion of ==, first create examples of strings and numbers, which was done earlier in this chapter:
var a = 42;
var b = "42";
a === b; // false
a == b; // true
As expected, the check a === b fails because the conversion is not allowed, and the values 42 and "42" are different.
However, in the second comparison a == b, non-strict equality is used; this means that if the types are different, the comparison algorithm will perform an implicit conversion of one
or both values.
But what kind of conversion is being performed here? Will the value a, that is, 42, become a string, or will the value b “42” become a number? The ES5 specification in sections 11.9.3.4–5 says:
- If Type (x) is of type Number, and Type (y) is of type String, return the result of the comparison x == ToNumber (y).
- If Type (x) is of type String and Type (y) is of type Number, return the result of the comparison ToNumber (x) == y.
In the specification, formal names of the Number and String types are used, while in the book for primitive types the notation number and string are usually used. Do not confuse the case of the Number symbol in the specification with the built-in Number () function. For our purposes, the case of characters in the name of the type does not play a role - they mean the same thing.
The specification says that the value “42” is converted to a number for comparison. About how the conversion is performed, it was already described earlier, and specifically when describing the abstract operation ToNumber. In this case, it is obvious
that the obtained two values of 42 are equal.
Comparison: anything with booleans
One of the most dangerous traps in implicit conversion of type == is encountered when trying to directly compare values with true or false.
Example:
var a = "42";
var b = true;
a == b; // false
Wait, what's going on here? We know that “42” is the true meaning (see earlier in this chapter). How is it that comparing it with true with the non-strict equality == operator
does not give true?
The reason is simple and deceptively cunning at the same time. It’s easy to misunderstand, many JS developers don’t make the effort to fully understand it.
Once again we quote the specification, sections 11.9.3.6–7:
- If Type (x) is of type Boolean, return the result of the comparison ToNumber (x) == y.
- If Type (y) is of type Boolean, return the result of the comparison x == ToNumber (y).
Let's see what is here. First step:
var x = true;
var y = "42";
x == y; // false
Type (x) really belongs to the Boolean type, so the ToNumber (x) operation is performed, which converts true to 1. Now condition 1 == “42” is calculated. Types are still different, therefore (almost recursively) the algorithm repeats; as in the previous case, “42” is converted to 42, and condition 1 == 42 is obviously false.
If you swap operands, the result will remain the same:
var x = "42";
var y = false;
x == y; // false
This time, Type (y) is of type Boolean, so ToNumber (y) gives 0. The condition "42" == 0 recursively turns into 42 == 0, which, of course, is false.
In other words, the value “42” is neither == true nor == false. At first glance, this statement seems completely unthinkable. How can meaning be neither true nor false?
But this is the problem! You are asking the wrong question. Although in fact it is not your fault, it is the brain that is deceiving you.
The value "42" is indeed true, but the construction "42" == true does not perform a boolean / transform test at all, whatever your brain says. "42" does not convert to boolean (true); instead, true is converted to 1, and then “42” is converted to 42.
Whether you like it or not, ToBoolean is not used at all here, so the truth or falsehood of “42” is not important at all for the == operation! It is important to understand how the == comparison algorithm behaves in all of the different type combinations. If the boolean value is on one side, then it is always converted to a number first.
If this seems strange to you, you are not alone. Personally, I recommend never, never, under any circumstances, to use == true or == false. Never.
But remember that I am only talking about == here. The constructions === true and === false do not allow type conversion, so they are protected from the hidden ToNumber conversion.
Example:
var a = "42";
// плохо (проверка не проходит!):
if (a == true) {
// ..
}
// тоже плохо (проверка не проходит!):
if (a === true) {
// ..
}
// достаточно хорошо (неявное преобразование):
if (a) {
// ..
}
// лучше (явное преобразование):
if (!!a) {
// ..
}
// тоже хорошо (явное преобразование):
if (Boolean( a )) {
// ..
}
If you avoid == true or == false (loose equality with boolean) in your code, you will never have to worry about this truth / falsity trap.
Comparison: null with undefined
Another example of an implicit conversion occurs when you use the lax == equality between null and undefined values. Again, I will quote the ES5 specification,
sections 11.9.3.2–3:
- If x contains null and y contains undefined, return true.
- If x contains undefined and y contains null, return true.
Null and undefined when compared with the non-strict operator == are equal to each other (that is, they are converted to each other), and no other values in the whole language.
For us, this means that null and undefined can be considered indistinguishable for comparison purposes, if you use the non-strict equality test operator ==, allowing their mutual implicit conversion:
var a = null;
var b;
a == b; // true
a == null; // true
b == null; // true
a == false; // false
b == false; // false
a == ""; // false
b == ""; // false
a == 0; // false
b == 0; // false
The conversion between null and undefined is safe and predictable, and no other value can give false positives for such a check. I recommend using this conversion so that null and undefined do not differ in the program and are interpreted as a single value.
Example:
var a = doSomething();
if (a == null) {
// ..
}
The a == null check only passes if doSomething () returns null or undefined and fails for any other value (including 0, false, and "").
The explicit form of this check, which prohibits any such type conversions, looks (in my opinion) much uglier and may work a little less efficiently!
var a = doSomething();
if (a === undefined || a === null) {
// ..
}
I believe that the form a == null is another example of a situation in which an implicit conversion makes it easier to read the code, but does it reliably and safely.
Comparison: objects and non-objects
If an object / function / array is compared with a simple scalar primitive (string, number or boolean), the ES5 specification says the following (section 11.9.3.8–9):
- If Type (x) is of type String or Number, and Type (y) is of type Object, return the result of the comparison x == ToPrimitive (y).
- If Type (x) is of type Object and Type (y) is of type String or Number, return the result of the comparison ToPrimitive (x) == y.
You may have noticed that in these sections of the specification only String and Number are mentioned, but not Boolean. The fact is that, as mentioned above, sections 11.9.3.6–7 ensure that any Boolean operand is first represented as Number.
Example:
var a = 42;
var b = [ 42 ];
a == b; // true
For the value [42], the abstract operation ToPrimitive is called (see "Abstract operations"), which gives the result "42". From this moment on, the simple condition “42” == 42 remains, which, as we have already found out, turns into 42 == 42, so that a and b are equal up to type conversion.
As you would expect, all the features of the abstract ToPrimitive operation discussed earlier in this chapter ((toString (), valueOf ()) are applicable in this case as well. This can be very useful if you have a complex data structure and want to define a specialized method valueOf () for it, which will have to provide a simple value for the purposes of checking equality.
Chapter 3 examined the “unpacking” of an object wrapper around a primitive value (as in new String (“abc”), for example), which returns the underlying primitive
value (“abc”). This behavior is related to the ToPrimitive transformation in the == algorithm:
var a = "abc";
var b = Object( a ); // то же, что `new String( a )`
a === b; // false
a == b; // true
a == b gives true because b is converted (or “unpacked”) by the ToPrimitive operation to the base simple scalar primitive value “abc”, which matches the value from a.
There are some values for which this is not so because of other overriding rules in the == algorithm. Example:
var a = null;
var b = Object( a ); // то же, что `Object()`
a == b; // false
var c = undefined;
var d = Object( c ); // то же, что `Object()`
c == d; // false
var e = NaN;
var f = Object( e ); // то же, что `new Number( e )`
e == f; // false
The null and undefined values cannot be packed (they do not have an equivalent object wrapper), so Object (null) is not fundamentally different from Object (): both calls create an ordinary
object.
NaN can be packaged in the equivalent object wrapper Number, but when == causes unpacking, the comparison NaN == NaN fails, because the value of NaN is never equal to itself (see chapter 2).
»More information about the book can be found on the publisher’s website
» Contents
» Excerpt
For Khabrozhiteley 25% discount on the coupon - JavaScript
After the payment of the paper version of the book, an electronic book is sent by e-mail.