Translator: Ten years on the trail
The original link
Over the past few months, I’ve made some improvements to JSHint. Primarily, learning ABOUT ES6 (I’m most proud of the re-implementation of variable scope) I came across several features that surprised me, most of which were ES6 features but some of which were ES3 features that I had never used before, And now I’m going to start using them.
Break from any code block
You should already know that you can break and continue from any loop — this is a fairly standard programming language construct. But what you may not realize is that you can add a label to the loop and break out of any layer loop:
outer: for(var i = 0; i < 4; i++) { while(true) { continue outer; }}Copy the code
The label feature also applies to breaks and continues. You’ve probably seen break in the switch statement:
switch(i) {
case 1:
break;
}
Copy the code
Incidentally, this is why Crockford suggests that your case should not be indented — because break jumps out of switch and not case, but I think indented case is much more readable. You can also add label to the switch statement:
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 can too).
{ { console.log(""I'm in an abritrary block""); }}Copy the code
Therefore, we can use label and break together to jump out of any code block.
outer: {
inner: {
if (true) {
break outer;
}
}
console.log(""I will never be executed"");
}
Copy the code
Note that this only works for break — you can only continue in a loop. I’ve never seen label used in JavaScript, and I wonder why — I think maybe because if I need to break two layers, it might be better to put this code block in a function, Then I can use a single layer break or an early return to achieve the same goal.
However, if I want to ensure that there is only one return statement per function (which is not my thing), THEN I can use brock with label. For example, look at the following function with multiple return statements:
function(a, b, c) {
if (a) {
if (b) {
return true;
}
doSomething();
if (c) {
return c;
}
}
return b;
}
Copy the code
If label is used:
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
There is another option, with more code 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 like the original version best, then the else version, and finally the label version — but maybe that’s because of my writing habits?
Deconstruct an existing variable
First of all, there’s a weird notation that I can’t explain. It looks like in ES3 you can add a parenthesis to a variable on the left side of a simple assignment without a problem:
var a;
(a) = 1;
assertTrue(a === 1);
Copy the code
If you can think of a reason why this is ok, please comment below!
Deconstruction is the process of pulling variables out of an array or an object. The most common examples are the following:
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
And you don’t have to use var or let or const. For arrays you can make the following code work as you expect:
var a;
[a] = array;
Copy the code
For objects, however, you must enclose the entire assignment statement in parentheses:
var a;
({a} = obj);
Copy the code
The reason for this is that you can’t tell whether the code is destructively assigned or block-level scoped without parentheses, because you can use anonymous blocks and ASI converts variables into executable expressions (as shown in the following example) Can have side effects…) This creates ambiguity.
var a = {
get b() {
console.log(""Hello!"");
}
};
with(a) {
{
b
}
}
Copy the code
Going back to the original example, we put parentheses around variables in our assignment statement — you might think it would also apply to deconstruction, but it doesn’t.
var a, b, c; (a) = 1; [b] = [2]; ({c} = { c : 3 });Copy the code
Deconstruct the value
Another aspect of deconstruction that you may not have realized is that attribute names don’t have to be unquoted strings, they can also be numbers:
`var {1 : a} = { 1: true }; `Copy the code
Or a quoted string:
`var {""1"" : a} = { ""1"": true }; `Copy the code
Or you might want to use a calculated expression as the name:
var myProp = ""1"";
var {[myProp] : a} = { [myProp]: true };
Copy the code
It’s easy to write confusing code:
var a = ""a"";
var {[a] : [a]} = { a: [a] };
Copy the code
Class declarations are block-scoped
Function declarations are enhanced, meaning you can write function declarations after function calls:
func();
function func() {
console.log(""Fine"");
}
Copy the code
Function expressions do the opposite, because when a variable is assigned, the variable declaration is promoted, but the assignment is not.
func(); Var func = function func() {console.log(" Fine"); };Copy the code
Classes have become a popular part of ES6, and have been widely touted as syntactic sugar for functions. So you might think the following code would work:
new func(); class func { constructor() { console.log(""Fine""); }}Copy the code
However, even though it’s basically syntactic sugar, the previous code doesn’t work. This is effectively equivalent to:
new func();
let func = function func() {
console.log(""Fine"");
}
Copy the code
This means that our func call is in a temporary dead zone (TDZ), which causes a reference error.
The same parameters
I didn’t think it was possible to specify parameters with the same name, however, it could!
function func(a, a) { console.log(a); } func(""Hello"", ""World""); / / output "" World" "Copy the code
Not in strict mode:
function func(a, a) { ""use strict""; console.log(a); } func(""Hello"", ""World""); -syntaxError: Strict mode function may not have duplicate parameter namesCopy the code
Typeof unsafe
Ok, I stole the conclusion of this article, but it’s worth emphasizing.
Before ES6, it was well known that using Typeof could always safely find out the definition of a variable, whether or not it was declared:
if (typeof Symbol ! == ""undefined"") {// Symbol can use} // the following code throws exceptions, if Symbol is not declared if (Symbol! == ""undefined"") { }Copy the code
However, this now works only when variables are declared without letting or const. Because of TDZ, it causes a reference error when a variable is not declared. Essentially, the variable is promoted to the beginning of the block-level scope, but any access prior to the declaration produces a reference error. In scope management for JSHint, I have to record the use of a variable that, if it is declared in the current block-level scope or its parent scope using let or const, will have a reference error in advance access. If it is declared using the var statement, then it is available, but JSHint will give a warning, and if it is not declared, then it uses the global scope, and JSHint may have another warning.
if (typeof Symbol ! == "") {reference error} let Symbol = true;Copy the code
The new array
I always avoid using the new Array constructor, partly because its argument can be either a length or a list of elements:
new Array(1); // [undefined] new Array(1, 2); / / [1, 2]Copy the code
But a colleague recently used it and came across something I hadn’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
The above code produces an array of 0 through 9. However, if you refactor it to use map:
var arr = new Array(10);
arr = arr.map(function(item, index) { return index; });
console.dir(arr);
Copy the code
The arR does not change after the above code is run. It seems that new Array(length) creates an Array with the specified length, but does not set any values, so referencing its length works, but enumerating elements does not. What 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 have an array where the eighth element is equal to 8, but all the other values are still undefined. Take a look at the Map’s polyfill implementation, which loops through each element (which is why index is correct), but uses in to check if an attribute is set. You get the same result if you use an array.
var arr = [];
arr[9] = undefined;
// or
var arr = [];
arr.length = 10;
Copy the code
other
Mozilla’s developer blog has a great article about the arrow function, which includes the use of