Over the last couple of months I’ve made a few enhancements to JSHint, mainly as a way of learning ES6 (I’m most proud of re-implementing variable scope detection) and during that process I’ve come across a few things that surprised me, mostly about ES6 but also in ES3, including the following feature, which I’ve never used and this is where I will start.
BREAK FROM ANY BLOCK
You should be aware that you can break and continue from any loop – it is a fairly standard programming language Construct. You might not be aware that You can label loops and jump out of any particular loop…
outer: for(var i = 0; i < 4; i++) { while(true) { continue outer; }}Copy the code
The same applies to both breaks and continues. You will have definitely seen break used with switch statements…
switch(i) {
case 1:
break;
}
Copy the code
Incidentally, this is why Crockford suggests your case should not be indented – the break jumps out of the switch, Not the case, but I prefer the readability of indenting case. You can also label switch statements…
myswitch: switch(i) {
case 1:
break myswitch;
}
Copy the code
Another thing you can do is create arbitrary blocks (I know you can do this in C#, I expect other languages too).
{ { console.log("I'm in an abritrary block"); }}Copy the code
So, we can put this together and label and break from arbitrary blocks.
outer: {
inner: {
if (true) {
break outer;
}
}
console.log("I will never be executed");
}
Copy the code
Note that this only applies to break – you can only continue in a loop block. I’ve never seen labels being used in JavaScript and I wondered Why I think it’s because if I need to break two layers of blocks, It’s a good sign that the block might be more readable inside a function and there I will use a single break or an early return to achieve the same thing.
However, if I wanted to write code that had a single return in every function, which is not to my taste, Then I could use block breaking. E.g. Take this multiple return function…
function(a, b, c) {
if (a) {
if (b) {
return true;
}
doSomething();
if (c) {
return c;
}
}
return b;
}
Copy the code
And use labels…
function(a, b, c) {
var returnValue = b;
myBlock: if (a) {
if (b) {
returnValue = true;
break myBlock;
}
doSomething();
if (c) {
returnValue = c;
}
}
return returnValue;
}
Copy the code
The alternative being more blocks…
function(a, b, c) {
var returnValue = b;
if (a) {
if (b) {
returnValue = true;
} else {
doSomething();
if (c) {
returnValue = c;
}
}
}
return returnValue;
}
Copy the code
I prefer the original, then using elses and then block labels – but maybe that’s because it’s what I’m used to?
DESTRUCTURING AN EXISTING VARIABLE
First off, a quirk I cannot explain. It seems in ES3 you can add parentheses around a simple assignment and It works…
var a;
(a) = 1;
assertTrue(a === 1);
Copy the code
If you can think of why anyone would do this, then please comment!
Destructuring is the process of pulling a variable out of an array or object. Most often you will see the following Examples…
function pullOutInParams({a}, [b]) {
console.log(a, b);
}
function pullOutInLet(obj, arr) {
let {a} = obj;
let [b] = arr;
console.log(a, b);
}
pullOutInParams({a: "Hello" }, ["World"]);
pullOutInLet({a: "Hello" }, ["World"]);
Copy the code
But you can also do it without a var/let/const. With Arrays you can just write it as you expect…
var a;
[a] = array;
Copy the code
But with objects you must surround the whole assignment in parenthesis…
var a;
({a} = obj);
Copy the code
The reasoning was that there was too much scope for confusion with code blocks, since you can have anonymous code blocks and ASI (automatic semi-colon insertion) will convert identifiers to Expressions that evaluate (and as shown in the example below, can have side effects…)
var a = {
get b() {
console.log("Hello!");
}
};
with(a) {
{
b
}
}
Copy the code
Going back to the original example, where we parenthesised our identifier before assignment – you might think that also applies to destructuring. It Doesn ‘t.
var a, b, c;
(a) = 1;
[b] = [2];
({c} = { c : 3 });
Copy the code
DESTRUCTURING WITH NUMBERS
Another aspect of destructuring you might not realise is that the property names do not have to be unquoted strings. They can be numbers..
`var {1 : a} = { 1: true }; `Copy the code
The Or quoted strings…
`var {"1" : a} = { "1": true }; `Copy the code
Or you might want to pull out a computed name…
var myProp = "1";
var {[myProp] : a} = { [myProp]: true };
Copy the code
Which makes it quite easy to write Code…
var a = "a";
var {[a] : [a]} = { a: [a] };
Copy the code
CLASS DECLARATIONS ARE BLOCK SCOPED
Functions are hoisted to their function scope, meaning you can have a function declaration after its usage…
func();
function func() {
console.log("Fine");
}
Copy the code
as opposed to function expressions which when assigned to a variable, the variable is hoisted but the assignment doesn’t happen until the function expression is assigned.
func(); // func is declared, but undefined, so this throws E.g. "func is not a function"
var func = function func() {
console.log("Fine");
};
Copy the code
Classes have been a popular part of ES6 and have been widely touted as syntactic sugar for functions. So you might think that the following will work..
new func(); class func { constructor() { console.log("Fine"); }}Copy the code
But even though this is basically syntactic sugar for our first example, it doesn’t work. It is actually equivalent to..
new func();
let func = function func() {
console.log("Fine");
}
Copy the code
Which means we are accessing func
in the temporal dead zone (TDZ), which is a reference error.
SAME NAME PARAMETERS
I assumed it was not possible to specify parameters with the same name, however, it is!
function func(a, a) {
console.log(a);
}
func("Hello", "World");
// outputs "World"
Copy the code
Except in strict mode…
function func(a, a) {
"use strict";
console.log(a);
}
func("Hello", "World");
// errors in chrome - SyntaxError: Strict mode function may not have duplicate parameter names
Copy the code
TYPEOF IS NOT SAFE
Okay, I stole this observation, but it is worth repeating.
Before ES6, it was well known you could always use typeof to safely find out if something was defined, Whether it was declared or not…
if (typeof Symbol ! == "undefined") { // Symbol is available } // The following throws an exception if Symbol not declared if (Symbol ! == "undefined") { }Copy the code
But now this only works if you have not declared the variable using let or const. This because of the TDZ, which makes it a reference error to access the variable before declaration. Essentially the variable is hoisted to the beginning of the block, But it is a reference error to access it. In JSHint’s scope Manager I have to record Performing of a variable, then if it is declared as a let or const within the current block or parent blocks, it is a reference error. If it is declared by a var statement it is valid but a JSHint warning and if it is not declared it is using a global and possibly a different warning.
if (typeof Symbol ! == "undefined") { // Symbol is available } let Symbol = true; // causes a reference errorCopy the code
NEW ARRAY
I’ve always avoided using the new Array constructor. Part of the reasoning is that its arguments can either be a length Or a list of items…
new Array(1); // [undefined] new Array(1, 2); / / [1, 2]Copy the code
But a colleague was using it recently and came across something I haven’t seen before..
var arr = new Array(10);
for(var i = 0; i < arr.length; i++) {
arr[i] = i;
}
console.dir(arr);
Copy the code
This produces an array of items from 0 to 9. But then, if This is refactored to use map…
var arr = new Array(10);
arr = arr.map(function(item, index) { return index; });
console.dir(arr);
Copy the code
Then arr
is unchanged. It seems that new Array(length)
creates an array with that length, but does not set any items, so referring to the length works, but enumerating does not. What about if I set a number ?
var arr = new Array(10);
arr[8] = undefined;
arr = arr.map(function(item, index) { return index; });
console.dir(arr);
Copy the code
Now I get an array where the 8th index is equal to 8, but all the others are set to undefined. Looking at the polyfill for map, it loops over every item (hence why index is correct) but uses an in check to see if the property is set. You also get The same behaviour using array literals…
var arr = [];
arr[9] = undefined;
// or
var arr = [];
arr.length = 10;
Copy the code
OTHER GEMS
Mozilla’s developer blog has a great post on arrow functions, which includes details of using