⬆️ ⬇️

Can a JavaScript construct (a == 1 && a == 2 && a == 3) be true?

Recently on Twitter and Reddit was walking an interesting piece of JavaScript code. The question connected with it was the following: “Can the expression (a==1 && a==2 && a==3) return true ?”. The answer to the question, oddly enough, was positive.



image



Today we will analyze this code and try to understand it.



Here he is:

')

 const a = { num: 0, valueOf: function() {   return this.num += 1 } }; const equality = (a==1 && a==2 && a==3); console.log(equality); // true 


If you are using Google Chrome, open the developer tools console using Ctrl + Shift + J on Windows, or Cmd + Opt + J on macOS. Copy this code, paste it into the console and make sure that the output really turns out true .



What is the catch?



In fact, there is nothing surprising here. Simply, this code uses two basic JavaScript concepts:





Lax equality operator



Notice that in the expression under study, (a==1 && a==2 && a==3) , a non-strict equality operator is used. This means that during the calculation of the value of this expression, the casting will be used, that is, using == you can compare values ​​of different types. I have already written a lot about this, so I will not go into details here. If you need to remember the features of the work of comparison operators in JS - refer to this material .



ValueOf () method



JavaScript has a built-in method for converting an object to a primitive value: Object.prototype.valueOf() . By default, this method returns the object for which it was called.



Create an object:



 const a = { num: 0 } 


As stated above, when we call valueOf() for an object a , it simply returns the object itself:



 a.valueOf(); // {num: 0} 


In addition, we can use typeOf() to check whether valueOf() actually returns an object:



 typeof a.valueOf(); // "object" 


We write the valueOf ()



The most interesting thing about working with valueOf() is that we can override this method in order to convert an object into a primitive value. In other words, you can use valueOf() to return objects instead of strings, numbers, booleans, and so on. Take a look at the following code:



 a.valueOf = function() { return this.num; } 


Here we have replaced the standard valueOf() method for object a . Now, when calling valueOf() , the value of a.num .



All this leads to the following:



 a.valueOf(); // 0 


As you can see, now valueOf() returns 0! The most important thing here is that 0 is the value that is assigned to the property of the a.num object. We can verify this by running several tests:



 typeof a.valueOf(); // "number" a.num == a.valueOf() // true 


Now let's talk about why this is important.



Non-Strict Equality Operation and Type Casting



When calculating the result of a non-strict equality operation for operands of different types, JavaScript will attempt to cast types - that is, it will attempt to cast (convert) operands to similar types or to the same type.



In our expression, (a==1 && a==2 && a==3) , JavaScript will try to bring object a to a numeric type before comparing it with a number. When performing a cast operation on a JavaScript object, it will first try to call the valueOf() method.



Since we changed the standard valueOf() method so that it now returns a.num , which is a number, we can now do the following:



 a == 0 // true 


Is the problem solved? Not yet, but nothing remains.



The assignment operator with addition



Now we need a way to systematically increase the value of a.num each time valueOf() called. Fortunately, JavaScript has an assignment operator with addition, or an incremental assignment operator ( += ).



This operator simply adds the value of the right operand to the variable on the left, and assigns the resulting value to this variable. Here is a simple example:



 let b = 1 console.log(b+=1); // 2 console.log(b+=1); // 3 console.log(b+=1); // 4 


As you can see, every time we use the assignment operator with addition, the value of the variable increases! We use this idea in our valueOf() method:



 a.valueOf = function() { return this.num += 1; } 


Instead of simply returning this.num , we will now, with each call to valueOf() , return the value of this.num , incremented by 1, and write the new value in this.num .



After this change is made to the code, we can finally try everything out:



 const equality = (a==1 && a==2 && a==3); console.log(equality); // true 


Works!



Step by Step Parsing



Remember that when using the non-strict equality operator, JS tries to perform a type conversion. Our object calls the valueOf() method, which returns a.num += 1 , in other words, returns a.num value, incremented by one each time it is called. Now it remains only to compare two numbers. In our case, all comparisons will be true .



It may be useful to consider what happens step by step:



 a                     == 1   -> a.valueOf()           == 1   -> a.num += 1            == 1   -> 0     += 1            == 1   -> 1                     == 1   -> true a                     == 2   -> a.valueOf()           == 2   -> a.num += 1            == 2   -> 1     += 1            == 2   -> 2                     == 2   -> true a                     == 3   -> a.valueOf()           == 3   -> a.num += 1            == 3   -> 2     += 1            == 3   -> 3                     == 3   -> true 


Results



We believe that examples like the one discussed above help, firstly, it’s better to learn the basic features of JavaScript, and secondly, they don’t let us forget that in JS not everything is what it seems.



Dear readers! If you are aware of any curiosities from the JavaScript field, please share them.



Source: https://habr.com/ru/post/347530/



All Articles