This blog post looks at a syntactic distinction that is unfortunately quite important in JavaScript: the difference between expressions and statements.
myvar 3 + x myfunc("a", "b")Roughly, a statement performs an action. Loops and if statements are examples of statements. A program is basically a sequence of statements (we’re ignoring declarations here). Wherever JavaScript expects a statement, you can also write an expression. Such a statement is called an expression statement. The reverse does not hold: you cannot write a statement where JavaScript expects an expression. For example, an if statement cannot become the argument of a function.
var x; if (y >= 0) { x = y; } else { x = -y; }Expressions have an analog, the conditional operator. The above statements are equivalent to the following statement.
var x = (y >= 0 ? y : -y);The code between the equals sign and the semicolon is an expression. The parentheses are not necessary, but I find the conditional operator easier to read if I put it in parens.
foo(); bar()For expressions, there is the lesser-known comma operator:
foo(), bar()That operator evaluates both expressions and returns the result of the second one. Examples:
> "a", "b" 'b' > var x = ("a", "b"); > x 'b' > console.log(("a", "b")); b
{ foo: bar(3, 5) }However, it is also a perfectly legal statement, with these components:
> [] + {} "[object Object]" > {} + [] 0Given that the plus operator is commutative, shouldn’t these two (expression) statements return the same result? No, because the second statement is equivalent to a code block followed by +[]:
> +[] 0For details on this and other WAT phenomena, consult [1].
JavaScript has stand-alone blocks? It might surprise you that JavaScript has blocks that can exist on their own (as opposed to being part of a loop or an if statement). The following code illustrates one use case for such blocks: You can give them a label and break from them.
function test(printTwo) { printing: { console.log("One"); if (!printTwo) break printing; console.log("Two"); } console.log("Three"); }Interaction:
> test(false) One Three > test(true) One Two Three
function () { }You can also give a function expression a name and turn it into a named function expression:
function foo() { }The function name (foo, above) only exists inside the function and can, for example, be used for self-recursion:
> var fac = function me(x) { return x <= 1 ? 1 : x * me(x-1) } > fac(10) 3628800 > console.log(me) ReferenceError: me is not definedA named function expression is indistinguishable from a function declaration (which is, roughly, a statement). But their effects are different: A function expression produces a value (the function). A function declaration leads to an action – the creation of a variable whose value is the function. Furthermore, only a function expression can be immediately invoked, but not a function declaration.
ExpressionStatement : [lookahead ∉ {"{", "function"}] Expression ;So what do you do if you want to write an expression statement that starts with either of those two tokens? You can put it in parentheses, which does not change its result, but ensures that it appears in an expression-only context. Let’s look at two examples: eval and immediately invoked function expressions.
> eval("{ foo: 123 }") 123 > eval("({ foo: 123 })") { foo: 123 }
> (function () { return "abc" }()) 'abc'If you omit the parentheses, you get a syntax error (function declarations can’t be anonymous):
> function () { return "abc" }() SyntaxError: function statement requires a nameIf you add a name, you also get a syntax error (function declarations can’t be immediately invoked):
> function foo() { return "abc" }() SyntaxError: syntax errorAnother way of guaranteeing that an expression is parsed in expression context is a unary operator such as + or !. But, in contrast to parentheses, these operators change the result of the expression. Which is OK, if you don’t need it:
> +function () { console.log("hello") }() hello NaNNaN is the result of applying + to undefined, the result of calling the function. Brandon Benvie mentions another unary operator that you can use: void [2]:
> void function () { console.log("hello") }() hello undefined
(function () {}()) (function () {}()) // TypeError: undefined is not a functionThis code produces an error, because JavaScript thinks that the second line is an attempt to call the result of the first line as a function. The fix is to add a semicolon:
(function () {}()); (function () {}()) // OKWith operators that are only unary (plus is both unary and binary), you can omit the semicolon, because automatic semicolon insertion kicks in.
void function () {}() void function () {}() // OKJavaScript inserts a semicolon after the first line, because void is not a valid way of continuing this statement [3].