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,var
The 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,let
The 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,const
The 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 callnew
Operator, 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 | ✅ this Is aFun The instance |
🔴 TypeError |
function | ✅ this Is aFun The 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 usenew
The function called returns another objectcover new
The 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