This quiz attracted quite a few more visitors than I'm used to :) Nice. Pity though that nobody wanted to write up some answers. At least, I didn't see anything come by. But don't worry, that's ok :)
So without any further delay, here are the answers to
last weeks quiz (if you didn't take it, check it out first!). Since this is more about learning than scoring points, I'll try to explain as much as I can about the questions. Hold on to your hat! :)
1: What's the scope lookup depth for the variables
e
,
f
,
g
and
h
when they are actually used?
var f;
try{}
catch(e){
try{
var h;
} catch(g){
e,f;
g,h;
}
}
e
: 2 (catch-catch)
f
: 3 (catch-catch-global)
g
: 1 (catch)
h
: 3 (catch-catch-global)
The problem I'm focussing on is
catch
scope.
catch
scope variables are tracked in their own scope, but other variables (even when declared in the
catch
clause) will still be tracked in their governing function (or global) scope. However, when looking up the
catch
scope does add another layer of scope because it must always check whether you're not accessing a
catch
variable. Tricky tricky.
Note that
h
and
f
are both declared in global scope. Also note that some browsers do not listen very closely to
catch
variables getting their own scope. They can
easily trip up with assignments, especially if the
catch
variable shadows a variable. It is therefore a best practice not to explicitly assign a new value to
catch
variables.
2: How do you get two succeeding forward slashes in a js source without creating a single line comment or having them be the body of another token?
/xx// x
/* *//* */
(and variations of this)While
//
normally always starts a single line comment, these are the few exceptional cases. They are a bit exotic, but still a reason not to blatantly check for
//
and assume a single line comment.
3: Is the semi after
while
in a
do ... while
required?
Yes. The grammer in the specification clearly defines it.
However,
browsers ignore this fact and will substitute a semi-colon as if it wasn't required (note that this isn't even ASI at work..).
4: When parsing tokens, which token are you more likely to encounter: the regular expression token or the division token?
Another weird question. And difficult to get the wording correctly. I'm sorry if you didn't get this question, it was part of my frustration when I first started writing a parser for js. I actually thought it was far more likely to encounter a division than a regular expression. So my "optimization" (for a full backtracking parser) was to search for the division first and the regular expression later.
When I wrote my handwritten parser, I was quite surprised to discover that the division can actually only follow a (sub-)expression and nowhere else. Regular expressions can go in front or after the division. By that definition alone, they are more likely to be encountered. Although the used terminology is debatable, so let's count no points for this one, k? I'll get you next time ;)
5: What should be the value of
e
after the following snippet?
var e = 5;
try {
var e = 6;
} catch(e) {
var e = 7;
}
6
As explained above, the
catch
scope variable has its own scope. The
var
statement is redundant as the variable exists in the first scope encountered.
6: For how many different type of things is the forward slash (
/
) used in js?
5
7: Name them ;) (from 6)
Agreed, 6 was a bit silly since you had to name them here anyways, but whatever:
- division
- regular expression
- multi line comments
- single line comments
- compound assignment
The only character being used for more reasons is assignment (=), although most of them are a compound assignment ;)
8: Is
l33t
a valid regular expression flag?
Yes. As you can read in
my tokenizer post, any valid identifier is a valid regular expression flag. So we can actually even do
/xyyxz/\u0067
and the parser should still swallow it. Of course, it'd be pretty silly. The result is still the canonical result (
/xyyxz/g
).
Note that unsupported regular expression flags can still throw an error, at least later in the program. Chrome ignores unknown flags and accepts the unicode escape as canonical. Firefox rejects both at parse time. Opera accepts unicode as the canonical but throws an error for unknown flags at parse time. Safari throws an error at parse time for unicode escapes, but ignores unknown flags. IE8 throws an error at parse time for both. Nice!
9: May a regular expression contain newlines?
No.
This was actually
proposed for ES4 and rejected. It's also been
proposed for Harmony (next js version).
10: May strings contain newlines?
Yes.
But only as part of a so called "line continuation". This means that any newline has to be preceeded by a backslash. More importantly, you should note that the newline will not actually be part of the string. So their presence is purely a visual thing for source code. While running the program, you cannot detect line continuations.
11: Is the space between
5
and
in
required?
12: Is the space between
return
and a string required?
13: Is the space between a division (
/
) and a regular expression required?
14: Is the space between
return
and a number required?
No, no, yes and yes.
These were sheer parsing questions. Tokenizer questions, even. You could have read about this in
my tokenizer post. Basically whitespace is only required to allow the tokenizer to distinguish between individual tokens. That would be a number, a keyword, a regular expression. The reason 13 is
no is because it would be a single line comment otherwise. This is an explicit exception in the specification ;) For 14 the reason is much simpler;
return5
is a valid identifier. This is not the case for
return"stuff"
; the tokenizer has no chance but to stop at the double quote and hope for the best :)
15: What regular expression matches the empty string?
/^$/
Ah, and by now you should know that
//
was
not going to cut it ;)
16: Can a backslash (
\
) occur somewhere in a variable name in the source code?
Yes.
Although you should probably never use it ;) But you can actually use unicode escapes to declare variable names! The resulting variable will be the so called "canonical" value of the name. That means that something like
ab\u0067de
results in a variable named
abgde
, and is completely valid. Note that it is still invalid to create variables with characters that would have been invalid as a literal. So no spaces in variable names.
17: What is the result of
f
after this statement?
new function f(){
f = 5;
};
5
If you thought it was a function, note that there was actually never a global function
f
because we're looking at a named function expression. Their name is never bound to the containing scope, only in the function itself. Regardless, the
new
keyword causes the function to be executed as a constructor immediately. And since it doesn't matter how a function gets invoked, the only thing that really happens in the snippet above is
that a _new_ global variable is created and assigned the value 5
. that the local variable f
(the function) gets replaced by the value 5
. So at the end, in global space, f
will not exist and throw a ReferenceError. Thanks Asen :)
18: Whats the result of x after running this snippet?
var x = 5;
function f(){
x = x;
var x;
}
f();
5
This should be very easy for the bufs. In fact, looking back at the specific example, probably not that difficult for most people. I guess what I was trying to depict was what would happen inside the function :p Anyways, in this example, the
x
inside the function is created when the function starts. So when you do
x=x
, the result is undefined, not
5
. Of course, that doesn't change the outer
x
so even if you fall for the trap, you might still get the right answer :) Ohwell, next time... next time!
19: May the
default
clause of a
switch
be placed before the last
case
clause?
20.a: If yes, are the remaining clauses used, ignored or do they cause an error?
20.b: If no, what happens if you do anyways?
Yes. In short, if you use
default
before the last
case
clause js will first try all cases. If none of them matched, it will go back to the default and fall through. It's a little unusual (at least in the languages I know) and should therefore probably not really be used (to prevent confusion). Check out
this blog post for more information.
21: How many arguments may an accessor getter have?
Zero.
The grammar only allows you to define a getter with no parameters. There's no point either, it is effectively used for reading from variable names. What would you expect the variable to hold?
22: How many arguments may an accessor setter have?
One.
The grammar requires you to define exactly one argument for setters. This will be the value that's assigned to the property that's the accessor. There's no point for more arguments and if there are none, you could never handle the assigned value (which has some valid use cases, but is silly nevertheless).
23: Are the parentheses required when invoking constructors?
No.
For some reason, the grammar in the specification defines the parentheses as optional. But since this confuses many developers, it might be best to use them regardless. Of course, if you're passing on any value, the parentheses are in fact required.
24: Are the parenthesis required when invoking functions?
Yes.
I was just trying to confuse you after 23.
You could technically argue that new
also invokes a function, only as a constructor, in which case the parens are optional.25: Are the parentheses required when invoking functions which are also constructors?
Yes.
If you weren't caught by 24 I was trying to at least make you doubt it now. But the fact is, all* functions are constructors. So the answer is the same as 24.
* Bound functions and built in functions are not always constructors, check the spec26: What happens if you declare a variable twice in the same scope?
Nothing (for the second declaration).
See, sometimes it's just that simple :)
27: Why does the empty array equal
false
?
This is a beauty and a genuine
wtfjs. The reason is coercion. The array is translated to a number. The
false
is then translated to a number. And since both are translated to
0
, the result of the comparison is
true
. So
[] == false
is
true
.
For more information about coercion see
this blog post or
this tool.
28: Must a number start with a digit?
No.
This wasn't very difficult. Most people will have at least seen something like
.5
. Numbers may start with a leading dot followed by the fraction.
Did you think the hex notation was the correct answer? Well it's not, because that still starts with a zero ;)
29: Why will
2.toString()
throw an error?
Because the tokenizer first parses
2.
as a number token and then
toString
as an identifier token. The parser sees a number followed by an identifier and cries wolf (throws error). The reason js will not accept this is because there's a parsing rule that says the tokenizer should take the longest possible consequtive characters when forming tokens.
2.
is longer than
2
and so it should parse the dot as part of the number. This would of course be fixed if numbers could not end with a dot, but it's a little too late for that now.
30: How do you fix the problem at #29?
These are the three methods I can think of:
- Add a space between the dot and the number. That way the tokenizer must stop at the space and the dot becomes an operator
- Add two dots. The first dot is parsed as part of the number, the second becomes an operator.
- Assign the number in a variable and call
toString
on the variable.
Basically, if you must really call methods on literals, there's probably a better way. Also, never use the whitespace method as there are several minifiers that don't take this case into account when removing whitespace. Let alone human refactor passes ;)
31: What's the longest expression in js you can think of that's still valid and only makes use of unique keywords, single character variables and (single) spaces?
Oh I should probably have prepared this. Please feel free to correct me on
twitter if you have a valid longer answer :) So here goes...
typeof delete new this in this instanceof this
Can you predict the answer? ;)
Note that you must wrap this in a function ran in the context of a(ny) constructor, in order for this to work... :) Possible caveats are
in
and
instanceof
. Especially
in
is not used in expressions very often. Also
void
is pretty useless in most scripts. Another trap was mentioning variable names. That might have set you on the wrong path since
this
,
null
,
false
and
true
are also keywords ;)
Fun fact:
function(){ typeof delete new this in this instanceof z; }
crashes Chrome (11) tabs when entered in the console.
32: What's the name of the property defined in following object literal?
{.3e-10:42}
3e-11
Numbers are valid property names. They are coerced to string and normalized. So
.3e-10
is normalized to remove the dot and add an extra zero to the exponent. That's the actual property name when you want to access the property (although you can access it through
.3e-10
too since the same transformation is applied).
33:
x=3?x=4:x=5
Oh come on. Assignment returns the assigned value. Which is
true
in all assignments in the expression, so the ternary goes to
x=4
which returns
4
.
34:
var x = new Array(5).map(function(){ return 0; })
The trap is that the array lambda methods do not call the callback for properties that don't exist.
new Array(5)
creates a so called sparse array, that is, an array with length of 5 but that doesn't actually have the properties 0 ~ 4. Since
map
does not call the callback for properties that don't exist, the callback is never called. Therefore
map
returns
an empty array. This bit me once too :)
35:
var x=5,3
As some people told me through twitter, this is in fact an error. Because the statement starts with
var
, you can only have it followed by either a variable name or a variable assignment. Since
3
is not a valid identifier, an error is thrown instead. (That was the intended answer for this question, btw)
36:
x=1,2
In an attempt to catch you off guard, the above will not throw an error.
x
is
1
.
In case you expected it to be
2
try to remember that the comma is stronger than all the infix (=binary) operators. Or is it weaker? I can never remember, they both make sense. Anyways, you always apply the comma last of the binary operators.
37:
var C = function(){return C;}; var c = new new C;
Yeah,
new new
is a little mindf***. But since
C
always returns itself (a constructor), even when invoked as a constructor (with
new
), the result is simply an instance of
C
. This is actually one of only a few constructs where
new new
will not throw an error. It's kinda pointless and silly anyways :)
38: function F(){ return 5; } var f = new F;
Trying to get you to wonder whether constructors are allowed to return
5
. The answer is no.
f
will be instance of
F
.
39: var obj; (obj={a:1}).b=2;
Through
the twitters, this gem by @cowboy caught my eye.
obj
will be an object literal "equal" to
{a:1,b:2}
. The reason is that
obj={a:1}
is first handled. It is put inside parentheses, which always return the value of its last expression. So after the assignment the object is returned and property
b
is set. Since objects are always a reference, the variable will also get the
b
property.
40:
(x) = 5;
41:
(1,x) = 5;
42:
(x,x) = 5;
5
, error, error
These questions illustrate the parenthesis even further but they try to prove a point. The parenthesis are mostly harmless. They expose a system that's pretty well hidden in js. In fact, from the top of my head I think this is the only way of exposing it. It's a principe called Reference. An internal specification notion that makes the difference between a variable and its value.
Basically, when you have
x
, it's a reference to a value in memory. When you have
(x)
, that doesn't change. But when you have
(x,y)
, the comma operator does an implicit "fetch" and the
y
variable stops being this Reference, but becomes the actual value. Which is returned by the parenthesis. So while 40 was perfectly fine, because the parenthesis returned the Reference, for 41 and 42 it returned whatever value
x
held at the time. To which you cannot assign. Thus, an error is thrown in both cases (it does not matter what's on the left hand side of the comma).
43:
var a = {f:function(){return 1;}}; var b = a.f();
44:
b
in
var a = {f:function(){return 1;}}; var b = (a.f)();
45:
var a = {f:function(){return 1;}}; var b = (1,a.f)();
1
,
1
,
1
Expected something else? So did I. I actually sucker punched myself here :p Had to
"duck" some irc channel before figuring it out myself. Ohhh, the shame. Anyways...
So 43 should be easy to deduct. The method of a gets called and returns
1
. In 44 and 45 I'm trying to confuse you with the above example. Certainly fooled me :p But because the function itself always returns
1
, the context only matters when fetching the function. So in
(1,a.f)()
the function
a.f
runs in global context, but that doesn't matter any more because when fetching the function,
a
was still taken into account.
46:
var n = NaN; b = n === n;
false
It's pretty well known that
NaN
doesn't match anything, even itself. As defined in the specification. The construct in the example above doesn't change that.
47:
var b = NaN === new Number(NaN);
48:
var b = isNaN(new Number(NaN));
false
,
true
This one came by
on twitter as well, by @jdalton. Somebody said it would have been stronger when assigned to a variable like the previous question. Guess I messed up. It's interesting because
isNaN(new Number(NaN))
is
true
, so you might expect it to be false to anything else. But since it's an object, the reference is equal to itself, regardless of the primitive value it represents.
49:
var b = (function(){return this}).call(1) instanceof Number;
true
So what happens here (at least in ES5) is that a function is created that returns
this
. It is immediately called with its context (
this
) set to
1
.
And 1
is of course an instance of Number
. Primitives are not instanceof
objects. What seems to happen here is that call
"boxes" the primitive to an object, which is instanceof Number
...50:
function f(){ break; }
Error. Break outside of switches and loops must have a (valid) label.
51:
x: function f(){}
Error. Labels can only have statements and a function _declaration_ is not a statement. Oops :) Note that it would still be invalid if there was a return between them. ASI does not "fix" it because it never introduces the empty statement.
52:
x: switch(y){ break x; }
Ok. You may break through a switch. Just as long as it's a valid and properly scoped label (scoped to statement, so to speak).
53:
x: x: debugger;
Ok. The label shadowing is wonky, but the parser doesn't care about duplicate labels.
fun fact: a: a: b;
crashes a chrome tab54:
x: while (y) x: continue x;
Ok. Continue may only occur inside loops but can have labels that are "closer" to its occurrence than the actual loop.
55:
x: var x;
Ok. It's perfectly fine to define a variable with the same name as a (reachable) label.
56:
x: var x = x; break x;
Ok. This is actually fine to do. Aside from the answer from the previous question, you can break to a label (as long as it's reachable from your current position) when not within a
switch
or a loop. Note that this does not cause infinite loop because the break makes the flow continue _after_ the "statement line" which was started by the target label.
continue
might have caused a loop, but that's only valid to use inside loops.
57:
x: try { throw y; } catch(e) { break x; }
Ok. Yes, you may even break through
try
-
catch
barriers.
58:
try { throw y; } catch(e) { x: } break x;
Error. Now you're pushing it. But only because you can only break to a label that's reachable from your current position. Labels are "scoped" by statements. It's a bit tricky to explain, but since you're not going to use them (RIGHT???) I'm not going to explain that in detail here. See also the next answer. Or play around on
zeonjs to get a sense of what's allowed and what's not.
59:
x: try { throw x; } catch(e) { break x; }
Ok. Since the
try
-
catch
starts with label
x
, it's perfectly fine to break to it from within it. That's the statement scope.
60:
if (x) a: debugger; if (y) break a;
Error.
a
is not a valid label that's reachable from the
break
position. Would have been ok if the label was in front of the second
if
.
61:
x:var x;break x;
Error. Label not reachable from that position.
62:
a = b && c = d
Error. Assignment is "stronger" than
&&
. So in effect, this is how browsers see it:
a = (b && (c = d))
.
63:
a & b = c
Ok. Assignment first,
&
second.
64:
a + b = c
Ok. Assignment first, addition second.
65:
a = b ? c = d : e = f
Ok. Slightly more complex, but the ternary is stronger than assignment. So assignment first, ternary second.
66:
a -- b
Error. Hopefully the next question lulled you into thinking it was subtraction and negation. But since the tokenizer rules require you to take the longest token, the tokenizer will take
--
rather than subtraction and negation. This results in three tokens which is missing a semi-colon. That's not right.
67:
a - - b
Ok. The
-
is also a unary operator, which converts its operand into a number and then makes it negative. So this is a substraction of a negated number.
68:
a- -b
Ok. Just more of the same.
69:
x: break x;
Ok. Covered in previous questions.
Alright. Those were the answers. Don't agree with an answer or one of the explanations I've given? Feel free to
let me know on twitter :)
Hope it helped you!