I have this line of code, but I still can't figure out the order in which the instructions are executed.
console.log((a=[]).join(a[1]=+(a[16]='!')+"").concat((a[0]=function(p){return p + a[1][1]})(' B'),'T',a[0]('M'),(a[1][0]+a[16])));
I would like to know how it works.
This is the result in a Snippet
$('#texto').text((a=[]).join(a[1]=+(a[16]='!')+"").concat((a[0]=function(p){return p + a[1][1]})(' B'),'T',a[0]('M'),(a[1][0]+a[16])));
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<p id="texto"></p>
TL;DR:
The goal of the code is to abuse (and in a terrible way) the language to concatenate the word NaN 17 times . NaN is a property of the global object in Javascript that is returned in operations or methods in the language to indicate that the result is not a number ( N ot- a - N umber), so the code also converts this expression to a string of text. It then concatenates (again, abusing the notation) the exclamation point and the word 'Batman!'.
exact explanation
Below I will try to explain step by step how the supplied code outputs the text string:
It is not necessary to explain that the important thing in the first part of the code is
console.log()
what it receives as an argument, which is the same as the one that receives the methodtext()
of the second part of the code with jQuery.The entire portion of the code (1) is known as an expression. In Javascript an expression produces or evaluates to a value (the result of the expression). An expression is a combination of operators that together contribute to the production of the value that the expression returns. These operators have a precedence - which determines which of all the operators in an expression is executed first - and an associativity - which determines the order of execution of operators that have the same level of precedence. Knowing the above is necessary since it allows us to determine which of all the -extensive- code of the previous single line is executed first.
The objective then is to convert all the previous code into an equivalent one that allows us to understand what it does. The code has three important parts:
How is it possible to determine this? Reading, from left to right, the three parts correspond to three operators:
(2+2)*3
to indicate that the operator is evaluated first+
and then the operator*
, otherwise Javascript evaluates the operator first*
and then the operator+
since the standard mentions that multiplication has higher precedence than addition.join()
andconcat()
.Since the grouping operator
( )
is a pair of parentheses surrounding an expression, what is marked A in the code above is an expression. In the same way, functionsjoin()
andconcat()
can (not have to) receive a value. For this example, they receive the values that are the result of expressions B and C. In short, we have three more expressions:The NaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaN part!
It is not hard to see that the most complex expression is C. Ignoring these three expressions for the moment, our goal is to see how the larger expression (2) is executed. From the precedence and associativity table in the Javascript documentation, we find the following:
( )
has the highest level of precedence.
has a lower precedence level than the grouping operator and associates from left to right (that is, for an expression likea.b.c
evaluates(a.b).c
and does nota.(b.c)
a.b().c()
execute as(a.b()).c()
and not asa.(b().c())
The above shows that our expression (2) is executed all the way from left to right (because they all have that associativity) starting with the grouping operator as
( )
having the highest precedence level. Let's then review the expression A that has this operator:The above expression (an assignment expression) creates an empty array and assigns it to the variable named
a
. Two things can happen:a
already exists, it assigns the created empty array to it.a
does not exist, it creates a named variablea
and assigns the created empty array to it.In Javascript one creates a variable usually with the keyword
var
, but this is optional. Variables usually have a scope -the blocks in which they can be accessed, modified, etc-. When a variable is created with the keywordvar
the scope of this variable is restricted to the place where it was created (for example a function), without the keywordvar
the scope of this variable is the global object, which is very similar to a variable global that can be accessed by any following code that is executed . Knowing this is important because the other expressions (B and C) in the code make use of this behavior to access the variable againa
.Now, since it is an expression, it returns a value. The standard mentions that assignment expressions return the assigned value. In this case, the value it returns is precisely the empty array created. this completes the first of three parts of the original (1) code.
Continuing reading from left to right we find the member operators
.
and the function calljoin()
. This means that we are accessing a function calledjoin()
from the object that was returned by the first expression. Consulting the documentation , it is found that the methodjoin()
is a function that is used to convert the objects of an array into a string. It makes sense that this function is called on the object returned by the previous expression because precisely, it returned an empty array. Since it's an array, we know we can call the functionjoin
on this object. The methodjoin()
receives as an argument another expression that evaluates to a separator that can be used to separate the objects of the array that is converted to a string, for example:Since the object this function is called on is an empty array, it doesn't seem to make sense to do this, but you have to see exactly what it receives as an argument. Recall that when receiving an expression as an argument, it can do anything before evaluating to a result (a side effect). Let's check then what expression receives the function
join()
(part B)This is, again, another expression that is made up of several operators:
The member operator
[]
, (similar to the member operator.
) used to access a property of an object. In an array it is normally used to access the element at position i, for example:Remember that in JavaScript, array indices start at 0.
The first operator
+
(not to be confused with the second+
). It is a unary operator (that is, it applies to a single operand). If you notice the only operand it applies to is the expression wrapped in the grouping operator (ie aa[16]='!'
). According to the documentation , this operator is responsible for trying to convert the value of an expression to a number, for example:If the operator cannot convert to a number, the return value is NaN , NaN means Not a Number and makes sense, if the variable representation cannot be converted to a number, the result of the operand is this variable .
The grouping operator
( )
. We had already seen what it does and what its precedence and associativity are. This operand has as expressiona[16]='!'
, which combines the member (a[16]
) and assignment (=
) operands.+
The binary operator . This operator can be either the usual arithmetic addition or string concatenation operator. Since it is binary it requires two operands, one of which is the result of the previous grouping operator expression and the other is the character string "!". Since one of the two is a string, the operator used is the string concatenation operator.Once the operators have been identified, we proceed to see which are executed first and in what order. Reviewing the precedence table again we find the following:
( )
has the highest precedence level and associates from left to right.+
has a lower precedence level than the grouping operator and associates from right to left+
has a lower precedence level than the unary operator and associates from left to right.=
has the lowest level of precedence and associates from right to left .Therefore, the order in which the entire expression is executed is as follows:
Having the highest precedence, the expression inside the grouping operator is executed first:
Do you remember that before this expression was executed, an array called
a
that was similar to a global variable had already been created? Well, here we access that array and assign the '!' character to it at position 16. Here it must be remembered that the arraya
had been created empty, unlike other programming languages in Javascript it is not necessary to specify the size of the array to insert elements, so the previous operation actually does two things: first, it inserts into the position 16 of the array the element and second, it implicitly gives the array a length: by assigning the element in position 16 we are indicating that the array has 17 elements (remember that we start from position 0).When the assignment expression finishes executing, it returns its value (that is, the character '!'). The precedence continues with the unary operator
+
. This associates from right to left, so it receives the operand (the character!
) and does its job: try to convert it to a number But here comes the important thing: it is not possible to convert the character '!' to a number, therefore, the result of this operation is NaN .The next precedence is given by the binary
+
concatenation operator. This operator associates from left to right, so it concatenates the NaN value with the empty string "". This procedure results in getting the text string "NaN" (the variable NaN is different from the text string "NaN".The lowest precedence occurs when assigning the above expression to position 1 of the array
a
. Remember that assignment expressions return the assigned value.At the end of the above we have created an array of 17 elements, where the last element has the character "!", and the second the string "NaN". At the same time, the expression returns the string "NaN" which is also the argument of the function
join()
. Remember that this function receives a parameter that it uses as a character separator to print all the elements of the array. At this point in execution, the fix looks similar to the following:What the function will do
join()
is take the element at position 0, concatenate it with the parameter ("NaN"), concatenate it with the element at position 1, concatenate it with the parameter, and so on until you get to the element at position 16. undefined data type in Javascript means that the type is unknown (not defined). The documentation mentions that the methodjoin()
finds an element of type undefined and converts it to an empty string. This means that it will concatenate empty strings with the word "NaN" 16 times plus the string "NaN" at position 2 plus the character "!" from the last position. And that explains how the first part of the chain originates.The Batman part!
The previous explanation showed how the expressions A and B of parts 1 and 2 manage to build the concatenation of NaN, remember that these expressions evaluate to a value. The result of the function execution
join()
is a text string (of type String). By following the precedence of the original code we now find the functionconcat()
. According to the documentation, this function combines text from two inputs and returns a new string product of the concatenation of these. This function can be called on objects of type string, which is precisely the data type of the string "NaNNaN..." returned by the functionjoin
. Like this one, it receives a value as a parameter, therefore an expression can be passed that evaluates to a value, which is precisely what the C expression does:I'm going to write the code in a slightly different way:
This expression is actually 4 expressions separated by the comma operator
(,)
. The objective of this operator is to separate a set of expressions associating them from left to right, the following are the 4 expressions that it separates, the first one is:With the aforementioned, it is not difficult to notice that the objective of the expression is to assign
a
an anonymous (unnamed) function to the first element of the array. Before seeing what this function returns, it is necessary to see what is in the element a 1 of the array. It is clear that the element 1 of the arraya
is the text string "NaN", the text strings are by themselves an array of characters, so the second element of the string is the character 'a', that is, a 1 returns the character 'a', so the function can also be written as follows.Now, the expression not only assigns the first element of the array to this function, but is called immediately if defined (a common pattern in Javascript). If you notice, the anonymous function is inside a grouping operator, so the declaration is defined first, then the same function is returned (because the expression returns the value that is assigned) and is executed with the parameter ' B '. By doing this, the function will return 'Ba' (the concatenation of the parameter 'B' with the character 'a'.
The second expression that separates the comma operator is
which is simply the character 'T'
The third expression that separates the comma operator is
Remember that in the first position of the array
a
there is an anonymous function that we can call, in this case we are calling the same function that we saw before but with the parameter 'M', it is clear that it will return the string 'Ma' (concatenate the parameter with the character 'a').The fourth expression that separates the comma operator is
This is again another expression (because of the grouping operator). The goal of the expression is to use the binary concatenation operator
+
to join what is in the second position of the arraya
(a character string: "NaN" and the first character of the string (the letter "N") with what is in the last position of the arraya
(the character "!"). Therefore, the expression returns the value 'N!'Now, all of these expressions are separated by commas when passed as a parameter to the function
concat()
. This function concatenates the string of characters that is called with each of the parameters that are sent separated by commas, from left to right, that is:As the word to which it is concatenated is:
and the function that solves `concat(' Ba','T','Ma','N!') is applied to it, the expression that is finally returned is the expected one:
Additional comments
What is the above for: absolutely not at all . Probably to demonstrate how to obfuscate code in Javascript (make it unreadable to other people), or as part of a technical test to see how well someone knows the language (because if you noticed a piece of code like that required explaining a lot of different topics) . In the end it is a complete abuse of the language and its notation and it is always preferable to build a clear and concise code that does the same thing, even if it is not so funny, compare for example with this code that does exactly the same as the original code:
The code is divided into 2 main parts:
The string
NaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaN!
is generated thanks to the functionjoin
.The string
BaTMaN!
is formed inside the functionconcat
.To form the chain
NaN
you just have to understand how it worksjoin
.join
allows you to convert an array into a text string, it receives some 'separator' as an argument:In the code we are analyzing it is used
NaN
as a separator:Now note that this
a
is an empty array; then, when executing( a[1] =+ (a[16] = '!') )
two interesting things happen:The result of this operation is effectively
NaN
being used by the functionjoin
as a separator.When executing
(a[16] = '!')
we are creating 17 spaces inside the arraya
, of which its first 16 positions are not assigned (and therefore its value isundefined
). While the last position contains a'!'
. Remember that this code is executed before itjoin
does its job.But why
NaN
is the result of this operation? Simple;NaN
stands for 'Not a number' and occurs when a mathematical operation results in something that is not a number. Example; you can't add5 + 'hola'
and expect the result of this operation to be a number, at least not in Javascript.In this case, we know that it
(a[16] = '!')
returns the value contained ina[16]
, that is'!'
, therefore ita[1] = +(a[16] = '!')
is the same asa[1] = +'!'
; We also know that the+
en+'!'
is a unary operator, which is going to try to convert the string'!'
to a number, and guess what happens if you try to convert the character '!' to number; exact,NaN
.IN SUMMARY , the chain of
NaN
can be easily formed as follows:It's like preparing a cooking recipe! (Which is not exactly easy haha).
As for
BaTMaN!
; the function stored ina[0]
concatenates an 'a' (the 'a' ofNaN
) to whatever you pass as a parameter,a[0](' B')
returnsBa
, concatenates a 'T' and it will be 'BaT', call the function againa[0]('M')
, you will get 'Ma' , concatenated to what you carry would be 'BaTMa', and finally create a new string with the 'N' ofNaN
+ '!'BaTMaN!
and you will have your