This blog post looks at a syntactic distinction that is unfortunately quite important in JavaScript: the difference between expressions and statements.
Statements and expressions
JavaScript distinguishes expressions and statements. An expression produces a value and can be written wherever a value is expected, for example as an argument in a function call. Each of the following lines contains an expression:
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.
Similar kinds of statements and expressions
The difference between statements and expressions becomes clearer if we look at members of the two syntactic categories that are similar.If statement versus conditional operator
The following is an example of an if statement:
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.
Semicolon versus comma operator
In JavaScript, one uses the semicolon to chain statements:
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
Expressions that look like statements
Some expressions look like statements. We’ll examine why that is a problem at the end of this section.Object literal versus block
The following is an object literal, an expression that produces an object.
{
foo: bar(3, 5)
}
However, it is also a perfectly legal statement, with these components:
- A block: a sequence of statements in curly braces.
- A label: you can prefix any statement with a label. Here the label is foo.
- A statement: the expression statement bar(3, 5).
> [] + {}
"[object Object]"
> {} + []
0
Given 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 +[]:
> +[]
0
For 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 expression versus function declaration
The code below is a function expression:
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 defined
A 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.
Using object literals and function expressions as statements
We have seen that some expressions are indistinguishable from statements. That means that the same code works differently depending on whether it appears in an expression context or a statement context. Normally the two contexts are clearly separated. However, with expression statements, there is an overlap: There, you have expressions that appear in a statement context. In order to prevent ambiguity, the JavaScript grammar forbids expression statements to start with a curly brace or with the keyword function:
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
eval parses its argument in statement context. If you want eval to return an object, you have to put parentheses around an object literal.
> eval("{ foo: 123 }")
123
> eval("({ foo: 123 })")
{ foo: 123 }
Immediately invoked function expressions (IIFEs)
The following code is an immediately invoked function expression.
> (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 name
If you add a name, you also get a syntax error (function declarations can’t be immediately invoked):
> function foo() { return "abc" }()
SyntaxError: syntax error
Another 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
NaN
NaN 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
Concatenating IIFEs
When you concatenate IIFEs, you must be careful not to forget semicolons:
(function () {}())
(function () {}())
// TypeError: undefined is not a function
This 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 () {}())
// OK
With 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 () {}()
// OK
JavaScript inserts a semicolon after the first line, because void is not a valid way of continuing this statement [3].
19 comments:
void is a good way to clearly express the return value of an immediately invoked function is unused while providing the utility of turning it into an expression. This also avoids any potential pitfalls that are possible when using parenthesis caused by missing semicolons on the previous line. A win in all ways. `void function iffe(){ "life is good inside an iffe " }()`
Makes sense, yes!
Great article, very informative
This is interesting – I can't find any solid documentation into `void`. Can you elaborate on its intended and practical effect?
Funny that you should ask: http://www.2ality.com/2011/05/void-operator.html
Also: https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Operators/void
Both are valid ways of writing an IIFE as an expression statement, yes. The only thing you need to make sure is that the statement doesn’t start with `function`. How you do that doesn’t matter. An IIFE has two parts: Part one is the function (a value), part two is the “call operator” (). Therefore you have the option to either parenthesize the whole thing or just the first part. Either way prevents the token `function` from starting the statement.
Agreed, I think void is a good standard to use for immediately invoked functions.
It's a unary operator that takes any expression and returns undefined. It also makes the function into an expression (same thing parenthesis do). So it's a good way to say up front what your intentions are, since there's no other possible thing you could do with the voided function besides immediately invoke it.
Illuminating! Thanks, the both of you. Together with Ben Alman's tract on IIFE's [1] this makes execution and scoping in JS miles clearer for me.
At which point, I'm definitely inclined to execute my immediately invoked functions with `void` — it's certainly no more mysterious to the first-time onlooker than returning unary operators or an extra parentheses, but once researched… The intention is much clearer — with no side-effects to boot.
1. "Statements perform an action." --- This implies that expressions don't perform an action, which is not true. (For example, `delete obj.prop` is an expression that deletes a property from an object, which (I think, we can agree) qualifies as an "action". Another example are assignment expressions, e.g. `x = 1` which set the value of a variable or property. That is also an action.) On the other hand, the empty statement does not perform an action, even though it's a statement. So, with both expressions, and statements, there are examples that do and don't perform an action. Therefore, in order to avoid confusion, I think it would be best if the phrase "performing an action" was not used in the context of statements vs expressions.
2. "In JavaScript, one uses the semicolon to chain statements" --- This implies that the semi-colon is a separator which appears between statements, which is not true. The semi-colon is part of the statement syntax. It belongs to the statement itself, and sequences of statements do not need any sort of separator. In order to "chain" statements, one simply writes them in sequence, with no seperator in between.
I don't get the difference between function expression and function declaration.
Firstly we have this:
You can also give a function expression a name and turn it into a named function expression:
function foo() { }
Then we have this:
If you add a name, you also get a syntax error (function declarations can’t be immediately invoked): > function foo() { return "abc" }()
Both code samples look similar (the second function is sort of immediately invoked). The first one is named a "function expression" but the second one is described as a "function declaration". Does the end parenthesis change the nature of the expression ?
Difference is: first one is called Crockford style, the other dogballs style.
It’s all about the context, the exact same code does something different, depending on whether it appears in statement context or in expression context.
- In statement context, function foo() {} declares a function foo. It is roughly equivalent to
var foo = function () {};
- In expression context, function foo() {} produces a value.
The parentheses guarantee expression context for what’s inside them. Only values can be called, which means that a function declaration cannot be used in an IIFE-like manner.
Both things are true! However, this article is not for experts, but for beginners (I doubt you learned much by reading it). Hence, I tried to make things as simple to understand as possible, at the cost of not being 100% precise. I can qualify the statement (1) (e.g. via “roughly”), but as a first idea of what the difference between expressions and statements is, I don’t think we can do much better.
I think the latter term is a Crockford-ism. He must be an interesting subject for Rorschach tests.
Very useful!
You forgot to mention the comma operator instead of the group operator ().
// Can be used instead of () for IIFEs
0, function(){}()
This keeps the return statement, and also enables you to use the Left-Hand-Side Expressions to call the function expression.
What WAT stands for?
This is the best explanation I could find: http://knowyourmeme.com/memes/wat
Post a Comment