Tried to figure out the working logic behind the results. I couldn't.
Could someone please help explain how Javascript works the output of each of the 4 situations and the reason behind it?
I think the 1st and 2nd are similar situations.
Thanks
var num = (1, 2, 3); // num = 3 (last number returned, No error generated)
console.log(num)
y = (1, 2, 3); // y = 3 (last number returned, No error generated)
console.log(y)
z = 1, 2, 3; // z = 1 (first number returned, No error generated)
console.log(z)
// Uncomment the following to run: (gives an error)
//var m = 1, 2, 3; // Error: Unexpected number
Your first three snippets are all basically the same. They demonstrate usage of the Comma Operator.
The Comma Operator a, b first evaluates a and throws away the result, then evaluates b and returns the result. It is left-associative, so a, b, c is the same as (a, b), c.
Observe:
const foo = () => { console.log("foo"); return 23; };
const bar = () => { console.log("bar"); return 42; };
const qux = () => { console.log("qux"); return 99; };
console.log((foo(), bar()));
// foo
// bar
// 42
console.log((foo(), bar(), qux()));
// foo
// bar
// qux
// 99
Note that I could have written the three functions like this, but of course it doesn't make much sense to use the Comma Operator while trying to explain it:
const foo = () => (console.log("foo"), 23);
const bar = () => (console.log("bar"), 42);
const qux = () => (console.log("qux"), 99);
console.log((foo(), bar(), qux()));
// foo
// bar
// qux
// 99
In your first snippet, you have a Variable Declaration statement, which declares a variable, evaluates the initializer, and binds the value of the initializer to the variable. The initializer in this case is 1, 2, 3, which is equivalent to (1, 2), 3. So, it will evaluate 1, throw away the result, evaluate 2 return the result 2 as the result of evaluating the sub-expression 1, 2 but then immediately throw it away, evaluate 3 and return the result 3 as the result of the evaluation.
Therefore, it binds the value 3 to the variable num.
The second snippet is exactly the same, except it is an Assignment Expression, not a Variable Declaration, i.e. in Strict Mode, it will assign to a variable named y that was previously declared with let or var and error out with a static error otherwise. In Loose Mode, it will assign to a variable named y that was previously declared with let or var, or to a property named y of the global implicit object otherwise.
The evaluation of the right-hand-side of the assignment is exactly the same as above.
Snippet #3 uses again the Comma Operator. The Comma Operator has higher precedence than the Assignment Operator (the way ECMAScript is specified, it does not contain a simple operator precedence table, rather, the precedence is implicit in the grammar, so you'll either have to read the entire grammar or believe me), so your snippet #3 is equivalent to
((z = 1), 2), 3;
Again, it works just the same as snippets #1 and #2: first, the expression z = 1 is evaluated (which has the side-effect of assigning 1 to z) and the result is thrown away, then the expression 2 is evaluated and the result is thrown away, then the expression 3 is evaluated, and the result is returned.
So, the result of the entire expression is 3, but since that result is not assigned to anything, not returned, not printed, it is simply thrown away. All that is left is the side-effect that happened while evaluating the first expression, namely assigning 1 to z.
The last expression is different, because it does not demonstrate the use of the Comma Operator. It is, again, a Variable Declaration, just like snippet #1. A Variable Declaration basically is a list of comma-separated identifiers with optional initializers. The reason you get an error here, is that 2 is not a valid identifier for a variable.
Observe:
var n = 1, o, p;
// Declares variables `n`, `o`, `p`, initializes `n` to `1`.
console.log(n, o, p);
// 1 undefined undefined
Now, for comparison, your snippet #4 is ecactly the same:
var m = 1, 2, 3;
// Declares variables `m`, `2`, `3`, but `2` and `3` are not legal names.
The major difference between this snippet #4 and the other three is that the comma means a completely different thing here. In snippets #1 to #3, it is the Comma Operator, whereas in snippet #4, it is part of the Variable Declaration syntax.
There are other places where the comma can mean something different, for example parameter lists (function foo(a, b, c) or (a, b, c) => bla) or argument lists (foo(1, 2, 3)). In both cases, the comma is not the Comma Operator but part of the parameter list or argument list syntax to separate the parameters or arguments.
Observe:
console.log(1, 2, 3);
// 1 2 3
console.log((1, 2, 3));
// 3
In var num = (1, 2, 3); num is being set to the value of the final expression in the set. It's like a shorthand to evaluate multiple expressions and return the last value.
y = (1, 2, 3); is the same thing when you're at top level scope.
z = 1, 2, 3; is three expressions, z = 1, 2, and 3. z is set to 1, but if you logged the output of that line, you'd get 3.
var m = 1, 2, 3; is a syntax error because in this situation, the interpreter is expecting a single var pattern like var m = 1, n = 2, o = 3.
Related
If I open a Node REPL, and enter this code, I can see that it evaluates to 3:
$ node
> { let a = 1 + 2; a }
3
>
However, this code does not work:
const result = { let a = 1 + 2; a };
I get this error:
const result = { let a = 1 + 2; a };
^
SyntaxError: Unexpected identifier
What am I missing here? Am I correct in assuming that the block evaluates to the last expression within it, or is Node's REPL misleading me here?
As you've seen in the Node REPL, a block evaluates to a value, and that value is usually the value of the last statement in the block.
In ECMAScript 6.0, a BlockStatement is defined as follows (with subscripts omitted for simplicity):
BlockStatement:
Block
Block:
{ StatementList }
StatementList:
StatementListItem
StatementList StatementListItem
StatementListItem:
Statement
Declaration
Per section 13.2.13, Note 2, "The value of a StatementList is the value of the last value producing item in the StatementList." A Block is evaluated to the value of its StatementList, and a BlockStatement is evaluated to the value of its Block.
Thus, Node is correctly evaluating your BlockStatement to the value of the last value producing item, which is a. This is not a bug, nor is it specific to Node.
The reason you are getting an error is because you are trying to use a BlockStatement in a context where it is not allowed.
const result = { let a = 1 + 2; a }; is a LexicalDeclaration, which is defined as follows (again, with subscripts omitted):
LexicalDeclaration:
LetOrConst BindingList ;
LetOrConst :
let
const
BindingList:
LexicalBinding
BindingList , LexicalBinding
LexicalBinding:
BindingIdentifier Initializer
BindingPattern Initializer
We also need the definition of Initializer:
Initializer:
= AssignmentExpression
As you can see, the part of your lexical declaration after the equal sign requires an AssignmentExpression. If you look through the grammar for expressions, you will see that BlockStatement is not an AssignmentExpression.
As of ES6.0, there is no way to use a BlockStatement direcly as an AssignmentExpression. However, if you want to evaluate one to an expression, you can use eval:
> const result = eval("{ let a = 1 + 2; a }");
> result
3
I don't recommend doing this under ordinary circumstances, but it is helpful for seeing how ES6.0 does indeed evaluate blocks to values.
In js braces represent an object which expects key: value pairs. You can get the result like this;
const result = {a: 1 + 2};
console.log(result.a);
I came through this fun quiz on the internet.
console.log((function(x, f = (() => x)){
var x;
var y = x;
x = 2;
return [x, y, f()]
})(1))
and the choices were:
[2,1,1]
[2, undefined, 1]
[2, 1, 2]
[2, undefined, 2]
I picked solution 2 TBH, basing that on that x has been redefined, y was declared and defined with no value, and that f has a different scope hence getting the global x memory spot than function x memory spot.
However, I tried it in jsbin.com
and I found it was solution 1, while I was not sure why that happened I messed with the function body and I removed var x from the function body, I found that the response changed to #3 which makes sense as x value changed and hence it showed x and f as 2 and y as 1 which was declared globally.
but still I can't get why it shows 1 instead of undefined.
but still I can't get why it shows 1 instead of undefined.
It's not just you. This is a deep, dark part of the specification. :-)
The key here is that there are two xs. Yes, really. There's the parameter x, and there's the variable x.
A parameter list containing expressions (like f's default value) has its own scope separate from the function body's scope. But prior to parameter lists possibly having expressions, having var x within a function with an x parameter had no effect (x was still the parameter, with the parameter's value). So to preserve that, when there's a parameter list with expressions in it, a separate variable is created and the value of the parameter is copied to the variable at the beginning of the function body. Which is the reason for this seemingly-odd (no, not just seemingly) odd behavior. (If you're the kind who likes to dive into the spec, this copying is Step 28 of FunctionDeclarationInstantiation.)
Since f's default value, () => x, is created within the parameter list scope, it refers to the parameter x, not the var.
So the first solution, [2, 1, 1] is correct, because:
2 was assigned to the var x in the function body. So at the end of the function, the var x is 2.
1 was assigned to y from the var x before x got the value 2, so at the end of the function, y is 1.
The parameter x's value has never changed, so f() results in 1 at the end of the function
It's as though the code were written like this instead (I've removed unnecessary parens and added missing semicolons):
console.log(function(param_x, f = () => param_x) {
var var_x = param_x;
var y = var_x;
var_x = 2;
return [var_x, y, f()];
}(1));
...I removed var x from the function body, I found that the response changed to #3...
#3 is [2, 1, 2]. That's correct, because when you remove the var x from the function, there's only one x, the parameter (inherited by the function body from the parmeter list). So assigning 2 to x changes the parameter's value, which f returns.
Taking the earier example with param_x and var_x, here's what it looks like if you remove the var x; from it:
console.log(function(param_x, f = () => param_x) {
var y = param_x;
param_x = 2;
return [param_x, y, f()];
}(1));
Here's an annotated description of the original code (with the extraneous parentheses removed and missing semicolons added):
// /---- the parameter "x"
// v vvvvvvvvvvv--- the parameter "f" with a default value
console.log(function(x, f = () => x) {
var x; // <=== the *variable* x, which gets its initial value from the
// parameter x
var y = x; // <=== sets y to 1 (x's current value)
x = 2; // <=== changes the *variable* x's value to 2
// +---------- 2, because this is the *variable* x
// | +------- 1, because this is the variable y
// | | +--- 1, because f is () => x, but that x is the *parameter* x,
// | | | whose value is still 1
// v v vvv
return [x, y, f()];
}(1));
Final note regarding your title:
declaring a variable twice in IIFE
The variable is only declared once. The other thing is a parameter, not a variable. The distinction is rarely important...this being one of those rare times. :-)
The tricky part of that code is that the => function is created as part of a default parameter value expression. In parameter default value expressions, the scope includes the parameters declared to the left, which in this case includes the parameter x. Thus for that reason the x in the => function is in fact the first parameter.
The function is called with just one parameter, 1, so when the => function is called that's what it returns, giving [2, 1, 1].
The var x declaration, as Mr Crowder points out, has the (somewhat weird, at least to me) effect of making a new x in the function scope, into which is copied the value of the parameter x. Without it, there's only the one (the parameter).
This snippet of JavaScript code alerts 1 as answer. Can anyone please explain how this code executes?
const b = [1,2,3];
const f = (a, ...b) => a+b;
alert( f( 1 ) );
There are a couple of things going on here. The main one is that you're shadowing b, so the b outside the function isn't used within it. Instead, within it, you're creating a new array (because you've used a rest parameter, ...b) and assigning it to the b parameter. Since you call f with just one parameter, that array is empty. 1+[] is "1" because when either of the operands to + isn't a primitive (arrays aren't primitives), it's coerced to a primitive, and coercing an array to a primitive (indirectly) results in doing a .join(",") on the array. With a blank array, .join(",") is "". Then, since one of the operands is a string, the other operand (1) is coerced to string ("1") and it does "1"+"" which is, of course, "1". (Details on that last bit in the spec.)
f(1) is the same as 1 + []
f(1,2,3) is the same as 1 + [2, 3]
That's all...
The first line const b = [1,2,3]; is not used because the b in the lambda expression is the argument, not the constant declared before.
You can reference variables in a function call, however, when you define a function expression, the parameters name do not refer to any variables.
You'll get the expected result if you call the function like this:
alert(f(1, b));
It takes the rest parameters ... as an array b.
While this is empty, it is converted to an empty string and both operands are treated as string, because if one is a string, then it adds all values as string.
The result is '1'.
const b = [1, 2, 3];
const f = (a, ...b) => a + '';
console.log(typeof f(1));
Reproducing this in my browser's development tools console looks like this:
> b = [1,2,3]
> f = (a, ...b) => a+b
> f(1)
< "1"
// so it results in the string 1, why's that?
// lets try logging what a and b are in the function
> g = (a, ...b) => console.log("a=%o, b=%o", a, b)
> g(1)
< a=1, b=[]
// ah, so b is an array, which is what the spread operator does
// (gathers all remaining arguments into an array, so the question
// is then what does JavaScript return for the number 1 added to an empty array?
> 1 + []
< "1"
This behavior is one of the many quirks of JavaScript when using the + operator on different types.
Sometimes in the internet I see a syntax that is strange to me. Something like:
console.log = console.error = console.info = console.debug = console.warn = console.trace = function() {}
How does this "equal" sequence work?
Thanks.
An assignment operator assigns a value to its left operand based on the value of its right operand.
Consider:
a = b = c = d = 5;
The expression is resolved right to left so:
d = 5 and c = d (which is 5), b = c (5) and so on.
In your example those console methods are all being (re)defined as an empty function.
See: MDN: Assignment Operators for more info.
With assignments, the operations are resolved from right to left. So the right most value will be populated into all the preceding variables.
What you describe can be easily explained by analogy using a simpler example:
// Normal variable assignment
var a, b;
a = 15;
b = 15;
console.log("a: "+a+" , b: "+b);
// Assing the same value to two variables
var c, d;
c = d = 15;
console.log("c: "+c+" , d: "+d);
// Assign the same value to two variables and make it a function
var e, f;
e = f = function(){ console.log("Hi!"); };
// Call both of the variables' functions
e(); f();
Starting from variables a and b, you then go to c and d that are given the same value. The takeaway here is that you can assign the same value to two variables and the expression will be evaluated from right to left, so in effect it's like assigning the two variables' values separately. However, this does not mean that chaning one will change the other as well.
Finally, see what happens with e and f. These are assigned a function instead of a value, so you can then call them as if they were functions.
Short version: Expression gets resolved from right to left. The assignment is by value, not by reference, meaning that changing one of the variables' value will not affect the others. Finally, if you assign a function to your variables, you can then use their names to call the function that is their value.
Playing with the Babel REPL to check how to use variable as object literals property name I found that
var y = { [X] : 'x', [Y] : 'y' };
// is translated to:
// ... _defineProperty definition ...
var _y;
var y = (_y = {}, _defineProperty(_y, X, 'x'), _defineProperty(_y, Y, 'y'), _y);
Now, I understand what is happening in _defineProperty, but someone could explain me what's happening inside the parentheses?
Are the statement inside () executed in order and only the last statement result is returned?
Yes. The comma operator evaluates all the expression and returns the result of the last one, e.g.
a(), b(), c(); // Calls `a`, `b` and `c`, and returns the value returned by `c`
In you case, the wrapping parentheses are also needed because of operator precedence.