# quote

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.

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

var num = 1; 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; return b; }}

var g = f(); g(); / / 11; 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;
}
Copy the code

}

f(); / / 2

## 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;
Copy the code

}

f(true); // 10 f(false); // undefined 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. The inner for loop overwrites variable I because all I 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); } 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 10 10 10 10 many JavaScript programmers are 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 Remember the capture variables 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); } We are used to this strange form. 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.

Now that you know there are some problems with var, this illustrates why we use let statements to declare variables. Let and var are written the same except for their names.

Let hello = “Hello, An!” ; 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) {// 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); 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; 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; }

// cannot call ‘foo’ before ‘a’ is declared // the runtime should throw error foo(); // Uncaught ReferenceError: Cannot access ‘a’ before initialization

let a; 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) {
    var x;
}
Copy the code

} In the above example, 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 Does not require two declarations that are both block-level scopes 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} does not mean that block-level scope variables cannot be declared using function scope 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;
Copy the code

}

f(false, 0); // 0 f(true, 0); // 100 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

} the correct result will be obtained because the I of the inner loop can mask the I of 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.

## Block level scope variable fetching

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() {
        return city;
    }
}

return getCity();
Copy the code

} Because we already have city in the city environment, we can 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); } will output the same result as expected:

0 1 2 3 4 5 6 7 8 9

Const const

Const declarations are another way of declaring variables.

const numLivesForCat = 9; 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–; Unless you use special methods to avoid it, the internal state of const variables is actually modifiable.

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.

# 5. 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 This creates two named variables first and second. Equivalent to using an index, but more convenient:

first = input[0]; second = input[1]; It is better to destruct the declared variable:

[first, second] = [second, first]; On function arguments:

function f([first, second]) { console.log(first); console.log(second); } f(input); 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] 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 or other elements:

let [, second, , fourth] = [1, 2, 3, 4];

## Object deconstruction

You can also deconstruct objects:

let o = { a: “foo”, b: 12, c: “bar” }; let { a, b } = o; 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 }); 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; Unwrapping 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]; 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” }; let search = { … defaults, food: “rich” }; Search 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 }; 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(); let clone = { … c }; clone.p; // ok clone.m(); // error!

# 7, new, this, class, function

# # this with the 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’ var say () {

SetName = function () {} this.setName = function () {} this.mouse = 'mouse' // Object public attribute this.listen = Function () {} // Constructor this.setName(name)Copy the code

}

Supertype.read = function () {}

// write: function () {// write: function () {// write: function () {// write: function () {// write: function () {// write: function () {// write: function () {// write: function () {//

var instance = new SuperType() new

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, you can use any function as a constructor of a class by preending the function call with the new operator.

# # # to 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 **## functions and classes

Function * *

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

// + new let bottle = new Bottle(‘bottle’); // ✅ valid: Bottle {name: “Bottle “} console.log(bottle.name) // Bottle

New let bottle1 = Bottle(‘ Bottle ‘); Console. log(bottle1.name); // Uncaught TypeError: Cannot read property ‘name’ of undefined console.log(window.name); // bottle

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

New let bottle1 = Bottle(‘ Bottle ‘); // 🔴 immediately failed // Uncaught TypeError: Class constructor Bottle cannot be invoked without ‘new’

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 i.e

new Fun()	Fun
Copy the code

Class ✅ this is a Fun instance 🔴 TypeError function ✅ this is a Fun instance 😳 this is window or undefined

The quirks of using new

The return is invalid

function Bottle() { return ‘Hello, AnGe’; }

Bottle(); // ✅ ‘Hello, AnGe’ new Bottle(); / / 😳 Bottle {}

Arrow function

For arrow functions, using new returns an error 🔴

const Bottle = () => {console.log(‘Hello, AnGe’)}; new Bottle(); // Uncaught TypeError: Bottle is not a constructor this behavior intentionally follows 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 () => {console.log(‘Hello, ‘ + this.name)}; } let anGe = new AnGe(); console.log(anGe()); // Hello, AnGe so ** arrow function does not 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.

Allows a function called with new to return another object to override the return value of new

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}

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) {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} 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

Give me a attention to give me a like is the greatest care