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