The introduction

JS series is tentatively scheduled for 27 chapters, from basic, to prototype, to asynchronous, to design pattern, to architecture pattern, etc. This is the first chapter: it is the summary of var, let, const, deconstruction, expansion, function.

Let is similar to var in many ways, but lets can help you avoid some of the problems common in JavaScript. Const is an enhancement to let that prevents reassignment of a variable.

A,varThe statement

We’ve always defined JavaScript variables through the var keyword.

var num = 1;
Copy the code

Defines a variable named num with a value of 1.

We can also define variables inside functions:

function f() {
    var message = "Hello, An!";

    return message;
}
Copy the code

And we can also access the same variables inside other functions.

function f() {
    var num = 10;
    return function g() {
        var b = num + 1;
        returnb; }}var g = f();
g(); / / 11;
Copy the code

In the above example, g can retrieve the num variable defined in f. Whenever g is called, it has access to the num variable in f. Even when g is called after f has already executed, it can still access and modify num.

function f() {
    var num = 1;

    num = 2;
    var b = g();
    num = 3;

    return b;

    function g() {
        return num;
    }
}

f(); / / 2
Copy the code

Scope rule

For those familiar with other languages, VAR declares some strange scoping rules. Look at the following example:

function f(init) {
    if (init) {
        var x = 10;
    }

    return x;
}

f(true);  / / 10
f(false); // undefined
Copy the code

In this case, the variable x is defined inside the if statement, but we can access it outside the statement.

This is because the var declaration can be accessed anywhere within the function, module, namespace, or global scope that contains it, and the code block that contains it has no effect on this. Some call this the var scope or function scope. Function parameters also use function scopes.

These scope rules can cause some errors. For one thing, declaring the same variable more than once does not give an error:

function sumArr(arrList) {
    var sum = 0;
    for (var i = 0; i < arrList.length; i++) {
        var arr = arrList[i];
        for (var i = 0; i < arr.length; i++) { sum += arr[i]; }}return sum;
}
Copy the code

It’s easy to see the problem here, because the inner for loop overwrites variable I, because all I’s refer to variables in the same function scope. As experienced developers are well aware, these issues can be missed during code review and cause endless trouble.

Capture variable quirks

Think quickly about what the following code returns:

for (var i = 0; i < 10; i++) {
    setTimeout(function() { console.log(i); }, 100 * i);
}
Copy the code

As an introduction, setTimeout executes a function after a delay of several milliseconds (waiting for other code to complete).

Well, take a look at the results:

10
10
10
10
10
10
10
10
10
10
Copy the code

Many JavaScript programmers are already familiar with this behavior, but if you’re confused, you’re not alone. Most people expect the output to look like this:

0
1
2
3
4
5
6
7
8
9
Copy the code

Remember the capture variable we mentioned above?

Every function expression we pass to setTimeout actually refers to the same I in the same scope.

Let’s take a moment to think about why. SetTimeout executes a function after a number of milliseconds, after the end of the for loop. After the for loop ends, the value of I is 10. So when the function is called, it prints 10!

A common workaround is to use an immediate function expression (IIFE) to capture the value of I at each iteration:

for (var i = 0; i < 10; i++) {
    (function(i) {
        setTimeout(function() { console.log(i); }, 100 * i);
    })(i);
}
Copy the code

We are used to this strange pattern. The I argument overrides the I in the for loop, but since we have the same name, we don’t have to change much of the code in the body of the for loop.

Second,letThe statement

Now that you know that var has some problems, it illustrates why variables are declared in let statements. Let and var are written the same except for their names.

let hello = "Hello, the An!";
Copy the code

The main difference is not in syntax, but in semantics, which we’ll delve into next.

Block scope

When you declare a variable with let, it uses lexical or block scope. Unlike variables declared with VAR, which can be accessed outside the function that contains them, block-scoped variables are not accessible outside the block or for loop that contains them.

function f(input) {
    let a = 100;

    if (input) {
        // a is referenced normally
        let b = a + 1;
        return b;
    }

    return b;
}
Copy the code

Here we define two variables a and b. A is scoped inside f, and B is scoped inside the if block.

Variables declared in catch statements have the same scope rules.

try {
    throw "oh no!";
}
catch (e) {
    console.log("Oh well.");
}

// Error: 'e' doesn't exist here
console.log(e);
Copy the code

Another feature of variables that have block-level scope is that they cannot be read or written before they are declared. Although these variables always “live” in their scope, the region until the code that declares them is a temporary dead zone. It is simply used to indicate that we cannot access them before the let statement:

a++; 
// Uncaught ReferenceError: Cannot access 'a' before initialization
let a;
Copy the code

Note that we can still get an owning block scoped variable before it is declared. We just can’t call that function before the variable is declared.

function foo() {
    return a;
}

// 'foo' cannot be called before 'a' is declared
// The runtime should throw an error
foo();
// Uncaught ReferenceError: Cannot access 'a' before initialization

let a;
Copy the code

For more information about temporary dead zones, see Mozilla Developer Network here.

Redefine and mask

We mentioned that when you use var declarations, it doesn’t care how many times you declare them; You’re only going to get 1.

function f(x) {
    var x;
    var x;

    if (true) {
        varx; }}Copy the code

In the example above, all x declarations actually refer to the same X, and this is perfectly valid code. This is often a source of bugs. The good news is that the LET statement is not so loose.

let x = 10;
let x = 20; 
// Uncaught SyntaxError: Identifier 'x' has already been declared
Copy the code

Two declarations that are both block-level scopes are not required to give an error warning.

function f(x) {
    let x = 100; 
    // Uncaught SyntaxError: Identifier 'x' has already been declared
}

function g() {
    let x = 100;
    var x = 100; 
    // Uncaught SyntaxError: Identifier 'x' has already been declared
}
Copy the code

This is not to say that block-level scoped variables cannot be declared using function scoped variables. Rather, block-level scoped variables need to be declared in markedly different blocks.

function f(condition, x) {
    if (condition) {
        let x = 100;
        return x;
    }

    return x;
}

f(false.0); / / 0
f(true.0);  / / 100
Copy the code

The act of introducing a new name into a nested scope is called masking. It’s a double-edged sword that can accidentally introduce new problems and fix bugs. For example, suppose we now rewrite the previous sumArr function with let.

function sumArr(arrList) {
    let sum = 0;
    for (let i = 0; i < arrList.length; i++) {
        var arr = arrList[i];
        for (let i = 0; i < arr.length; i++) { sum += arr[i]; }}return sum;
}
Copy the code

I’ll get the right answer, because I in the inner loop can mask I in the outer loop.

Masking should generally be avoided because we need to write clean code. There are also scenarios where you can use it, and you need to think about it.

Get block-level scope variables

When we first talked about getting a variable declared with VAR, we briefly explored how it behaves once it has been retrieved. Intuitively, each time a scope is entered, it creates an environment for a variable. Even after the scoped code has been executed, the environment and its captured variables still exist.

function theCityThatAlwaysSleeps() {
    let getCity;

    if (true) {
        let city = "Seattle";
        getCity = function() {
            returncity; }}return getCity();
}
Copy the code

Because we already got city in the city environment, we can still access it even after the if statement is finished.

Recall from the setTimeout example, we ended up using an immediately executed function expression to get the state in each iteration of the for loop. In effect, what we do is create a new variable environment for the fetched variables.

Let declarations have completely different behavior when they appear in the body of the loop. Not only is a new variable environment introduced into the loop, but a new scope is created for each iteration. This is what we do when we use function expressions that execute immediately, so in the setTimeout example we just use the let declaration.

for (let i = 0; i < 10 ; i++) {
    setTimeout(function() {console.log(i); }, 100 * i);
}
Copy the code

Outputs the same results as expected:

0
1
2
3
4
5
6
7
8
9
Copy the code

Three,constThe statement

Const declarations are another way of declaring variables.

const numLivesForCat = 9;
Copy the code

They are similar to the LET declaration, but as the name suggests, they cannot be changed once assigned. In other words, they have the same scoped rules as lets, but they cannot be reassigned.

This makes sense; the values they reference are immutable.

const numLivesForCat = 9;
const kitty = {
    name: "Aurora".numLives: numLivesForCat,
}

// Error
kitty = {
    name: "Danielle".numLives: numLivesForCat
};

// all "okay"
kitty.name = "Rory";
kitty.name = "Kitty";
kitty.name = "Cat";
kitty.numLives--;
Copy the code

Unless you use special methods to avoid it, the internal state of const variables is actually modifiable.

Four,let vs. const

Now that we have two declarations with similar scope, it is natural to ask which should be used. As with most general questions, the answer is: it depends.

Using the principle of least privilege, all variables should be const except those you plan to modify. The basic rule is that if a variable doesn’t need to be written to, then anyone else using the code can’t write to it, and think about why it needs to be reassigned. Using const also makes it easier to predict the flow of data.

In your own judgment, consult with team members if appropriate.

Five, the deconstruction

Deconstruction array

The simplest way to deconstruct an array is to destruct an assignment:

let input = [1.2];
let [first, second] = input;
console.log(first); / / 1
console.log(second); / / 2
Copy the code

This creates two named variables first and second. Equivalent to using an index, but more convenient:

first = input[0];
second = input[1];
Copy the code

It is better to destruct the declared variable:

[first, second] = [second, first];
Copy the code

On function arguments:

function f([first, second]) {
    console.log(first);
    console.log(second);
}
f(input);
Copy the code

You can use… in arrays. Syntax creates remaining variables:

let [first, ...rest] = [1.2.3.4];
console.log(first); / / 1
console.log(rest); // [2, 3, 4]
Copy the code

Of course, since it’s JavaScript, you can ignore trailing elements that you don’t care about:

let [first] = [1.2.3.4];
console.log(first); / / 1
Copy the code

Or other elements:

let [, second, , fourth] = [1.2.3.4];
Copy the code

Object to deconstruct

You can also deconstruct objects:

let o = {
    a: "foo".b: 12.c: "bar"
};
let { a, b } = o;
Copy the code

This creates a and B through O.A and O.B. Notice, if you don’t need c you can ignore it.

Like array deconstruction, you can use undeclared assignments:

({ a, b } = { a: "baz".b: 101 });
Copy the code

Note that we need to enclose it in parentheses, because Javascript usually parses statements starting with {into a block.

You can use… in objects. Syntax creates remaining variables:

let{ a, ... passthrough } = o;let total = passthrough.b + passthrough.c.length;
Copy the code

Six, a

The unfold operation is the opposite of deconstruction. It allows you to expand an array into another array, or an object into another object. Such as:

let first = [1.2];
let second = [3.4];
let bothPlus = [0. first, ... second,5];
Copy the code

This gives bothPlus a value of [0, 1, 2, 3, 4, 5]. The expansion creates a shallow copy of first and second. They are not changed by expansion operations.

You can also expand objects:

let defaults = { food: "spicy".price: "?".ambiance: "noisy" };
letsearch = { ... defaults,food: "rich" };
Copy the code

The search value is {food: “rich”, price: “?” , ambiance: “Noisy”} Object expansion is much more complex than array expansion. Like array expansion, it is processed from left to right, but the result is still an object. This means that the attributes that appear after the expanded object override the previous attributes. So, if we modify the above example and expand at the end:

let defaults = { food: "spicy".price: "?".ambiance: "noisy" };
let search = { food: "rich". defaults };Copy the code

So, the food property in Defaults will rewrite food: “rich”, which is not what we want in this case.

There are other unexpected limitations to object expansion. First, it contains only enumerable properties of the object itself. Basically, when you expand an object instance, you lose its methods:

class C {
  p = 12;
  m() {
  }
}
let c = new C();
letclone = { ... c }; clone.p;// ok
clone.m(); // error!
Copy the code

New, this, class, function

With this new

The object created by the new keyword is actually a continuous assignment to the new object this and points __proto__ to the object pointed to by the class’s prototype.

var SuperType = function (name) {
    var nose = 'nose' // Private attributes
    function say () {} // Private methods
    
    // Privileged methods
    this.getName = function () {} 
    this.setName = function () {}
    
    this.mouse = 'mouse' // Object public attributes
    this.listen = function () {} // Object public method
    
    / / the constructor
    this.setName(name)
}

SuperType.age = 10 // Class static public properties (objects cannot be accessed)
SuperType.read = function () {} // Class static public method (object not accessible)

SuperType.prototype = { // Object assignment (can also be one by one)
    isMan: 'true'.// Public attributes
    write: function () {} // Public method
}

var instance = new SuperType()
Copy the code

Adding new before a function call is equivalent to treating SuperType as a constructor (even though it’s just a function), then creating a {} object and pointing this in SuperType to that object so that you can set something in a form like this.mouse. And then return that object.

Specifically, just add the prefix before the function callnewOperator, you can use any function as a class constructor.

Add new

In the above example, we can see that private variables or methods defined in the constructor, as well as static public properties and methods defined by the class, are not accessible in the instance object of new.

Do not add new

If you call SuperType() without new, this will point to something global and useless (like window or undefined), so our code will crash or do something stupid like setting window.mouse.

let instance1 = SuperType(); 

console.log(instance1.mouse); 
// Uncaught TypeError: Cannot read property 'mouse' of undefined

console.log(window.mouse); 
// mouse
Copy the code

Function, class,

function
function Bottle(name) {
  this.name = name;
}

// + new
let bottle = new Bottle('bottle'); // ✅ valid: Bottle {name: "Bottle "}
console.log(bottle.name) // bottle

/ / do not add new
let bottle1 = Bottle('bottle');   // 🔴 this call method is hard to understand
console.log(bottle1.name); // Uncaught TypeError: Cannot read property 'name' of undefined
console.log(window.name); // bottle
Copy the code
class
class Bottle {
  constructor(name) {
    this.name = name;
  }
  sayHello() {
    console.log('Hello, ' + this.name); }}// + new
let bottle = new Bottle('bottle');
bottle.sayHello(); // ✅ is still valid, print: Hello, bottle

/ / do not add new
let bottle1 = Bottle('bottle'); // 🔴 fails immediately
// Uncaught TypeError: Class constructor Bottle cannot be invoked without 'new'
Copy the code
Compared to using
let fun = new Fun();
// ✅ if Fun is a function: valid
// ✅ if Fun is a class: still valid

let fun1 = Fun(); // We forgot to use 'new'
// 😳 If Fun is a method that looks like a constructor: confusing behavior
// 🔴 If Fun is a class: fail immediately
Copy the code

namely

new Fun() Fun
class ✅ thisIs aFunThe instance 🔴 TypeError
function ✅ thisIs aFunThe instance 😳 this 是 window 或 undefined

The quirks of using new

The return is invalid
function Bottle() {
  return 'Hello, AnGe';
}

Bottle(); / / ✅ 'Hello, AnGe
new Bottle(); / / 😳 Bottle {}
Copy the code
Arrow function

For arrow functions, using new returns an error 🔴

const Bottle = (a)= > {console.log('Hello, AnGe')};
new Bottle(); // Uncaught TypeError: Bottle is not a constructor
Copy the code

This behavior is intentional following the design of the arrow function. A side effect of the arrow function is that it does not have its own this value — this resolves from the nearest normal function:

function AnGe() {
    this.name = 'AnGe'
    return (a)= > {console.log('Hello, ' + this.name)};
}
let anGe = new AnGe();
console.log(anGe()); // Hello, AnGe
Copy the code

So the ** arrow function doesn’t have its own this. ** But that means it’s completely useless as a constructor!

Conclusion: arrow function

  • This refers to the environment at the time of the definition.
  • Cannot be new instantiated.
  • This is immutable.
  • There are no Arguments objects.
Allow one to usenewThe function called returns another objectcover newThe return value of the

Let’s start with an example:

function Vector(x, y) {
  this.x = x;
  this.y = y;
}

var v1 = new Vector(0.0);
var v2 = new Vector(0.0); 

console.log(v1 === v2); // false
v1.x = 1;
console.log(v2); // Vector {x: 0, y: 0}
Copy the code

There’s nothing to say about this example, at a glance.

So let’s look at the following example and think about why b === c is true 😲 :

let zeroVector = null;
// create a lazy variable zeroVector = null;
function Vector(x, y) {
  if(zeroVector ! = =null) {
    // reuse the same instance
    return zeroVector;
  }
  zeroVector = this;
  this.x = x;
  this.y = y;
}

var v1 = new Vector(0.0);
var v2 = new Vector(0.0); 

console.log(v1 === v2); // true
v1.x = 1;
console.log(v2); // Vector {x: 1, y: 0}
Copy the code

This is because JavaScript allows a function called with new to return another object to override the return value of new. This can be useful when we use things like the object pool pattern to reuse components.

Reference:

TypeScript Variable Declarations

series

  • JS series 1: var, let, const, deconstruct, expand, new, this, class, function
  • JS series 2: Constructor, Prototype,proto, [[Prototype]] and Prototype chain
  • JS series 3: Six implementations of inheritance
  • JS series 4: An in-depth look at the Instanceof operator

For more on this series,Click on the Github homepage

Go last, welcome to pay attention to: front-end bottle jun, daily update