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

Original author: Brandon Morelli
  • Transfer
Recently, an interesting piece of JavaScript code walked on Twitter and Reddit . The question associated with him was: "Can the expression be (a==1 && a==2 && a==3)returned true?" The answer to the question, oddly enough, was positive. Today we will analyze this code and try to understand it.

image



Here he is:

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

If you use Google Chrome, open the developer tool console using the keyboard shortcut Ctrl + Shift + Jon Windows or Cmd + Opt + JmacOS. Copy this code, paste it into the console and make sure that the output really works true.

What is the catch?


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

  • The operator of lax equality.
  • Object method valueOf().

Strict Equality Operator


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

ValueOf () method


In JavaScript there is a built-in method to convert 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 the valueOf()object actually returns:

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

We write our valueOf ()


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

a.valueOf = function(){
  returnthis.num;
}

Here we have replaced the standard method valueOf()for the object a. Now the call valueOf()returns a value a.num.

All this leads to the following:

a.valueOf();
// 0

Apparently, now valueOf()returns 0! The most important thing here is that 0 is the value that is assigned to the property of the object a.num. We can verify this by performing 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 various types, JavaScript will try to cast types - that is, it will try to cast (convert) the operands to similar types or to the same type.

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

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

a == 0
// true

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

Assignment Operator with Addition


Now we need a way to systematically increase the value a.numeach time it is called valueOf(). Fortunately, JavaScript has an assignment operator with addition, or an additional assignment operator ( +=).

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

let b = 1console.log(b+=1); // 2console.log(b+=1); // 3console.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 method valueOf():

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

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

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

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

Works!

Step by step analysis


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

It may be useful to consider what is happening 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

Summary


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

Dear readers! If you know about any oddities from the field of JavaScript - please share them.


Also popular now: