1. Let the order

First, there is no variable promotion

// var case console.log(foo); Var foo = 2; //letThe situation of the console. The log (bar); / / error ReferenceErrorlet bar = 2;Copy the code

Temporary dead zone

var tmp = 123;

if (true) {
  tmp = 'abc'; // ReferenceError
  let tmp;
}Copy the code

Some “dead zones” are hidden and hard to spot.

function bar(x = y, y = 2) {
  return[x, y]; } bar(); / / an errorCopy the code

In the above code, calling bar fails (and some implementations may not) because the default value of parameter x is equal to another parameter y, which has not yet been declared and is a “dead zone.” If y defaults to x, no error is reported because x is already declared.

Repeat statements are not allowed

Let does not allow the same variable to be declared twice in the same scope.

2. Block level scope

In the first scenario, the inner variable may override the outer variable.

var tmp = new Date();

function f() {
  console.log(tmp);
  if (false) {
    var tmp = 'hello world';
  }
}

f(); // undefined
Copy the code

The if block uses the TMP variable in the outer layer and the TMP variable in the inner layer. The TMP variable in the inner layer overwrites the TMP variable in the outer layer.

In the second scenario, loop variables used to count are exposed as global variables.

var s = 'hello';

for(var i = 0; i < s.length; i++) { console.log(s[i]); } console.log(i); / / 5Copy the code

In the code above, the variable I is used only to control the loop, but when the loop ends, it does not disappear and leaks out as a global variable.

Let actually adds block-level scope to JavaScript.

function f1() {
  let n = 5;
  if (true) {
    let n = 10;
  }
  console.log(n); // 5
}
Copy the code

The above function has two code blocks that declare the variable n and print 5. This means that the outer code block is not affected by the inner code block. If you use var to define n both times, the output is 10.

3. The const command

First, basic usage

Const declares a read-only constant. Once declared, the value of a constant cannot be changed.

Second, the nature of

What const actually guarantees is not that the value of the variable cannot be changed, but that the data stored at the memory address to which the variable points cannot be changed. For data of simple types (values, strings, booleans), the value is stored at the memory address to which the variable points, and is therefore equivalent to a constant. But for complex type data (mainly objects and arrays), variable pointing to the memory address, save only a pointer to the actual data, const can guarantee the pointer is fixed (i.e., always points to the other a fixed address), as far as it is pointing to the data structure of variable, is completely unable to control. Therefore, declaring an object as a constant must be done with great care.

const foo = {}; // Add an attribute to foo, which succeeds foo.prop = 123; Foo. Prop // 123 // Pointing foo to another object will result in an error foo = {}; // TypeError:"foo" is read-only
Copy the code

In the code above, the constant foo stores an address that points to an object. Only the address is immutable, that is, you can’t point foo to another address, but the object itself is mutable, so you can still add new attributes to it.

4. Deconstruction

Array deconstruction

ES6 allows you to extract values from arrays and objects and assign values to variables in a pattern called Destructuring.

Previously, to assign a value to a variable, you had to specify a value directly.

let a = 1;
let b = 2;
let c = 3;
Copy the code

ES6 allows you to write it like this.

let [a, b, c] = [1, 2, 3];
Copy the code

The above code shows that you can extract values from an array and assign values to variables in their respective locations.

Essentially, this is “pattern matching,” in which the variable on the left is assigned a corresponding value as long as the pattern on both sides of the equal sign is the same. Here are some examples of destructuring using nested arrays.

let [foo, [[bar], baz]] = [1, [[2], 3]];
foo // 1
bar // 2
baz // 3

let [ , , third] = ["foo"."bar"."baz"];
third // "baz"

let [x, , y] = [1, 2, 3];
x // 1
y // 3

let [head, ...tail] = [1, 2, 3, 4];
head // 1
tail // [2, 3, 4]

let [x, y, ...z] = ['a'];
x // "a"
y // undefined
z // []
Copy the code

If the deconstruction fails, the value of the variable is equal to undefined

Object deconstruction

let { foo, bar } = { foo: 'aaa', bar: 'bbb' };
foo // "aaa"
bar // "bbb"
Copy the code

Object deconstruction differs from arrays in one important way. The elements of an array are arranged in order, and the value of a variable is determined by its position. The attributes of an object have no order, and variables must have the same name as the attributes to get the correct value.

Like arrays, deconstruction can also be used for nested structured objects.

let obj = {
  p: [
    'Hello',
    { y: 'World'}};let { p: [x, { y }] } = obj;
x // "Hello"
y // "World"
Copy the code

Note that p is a mode, not a variable, and therefore will not be assigned. If p is also assigned as a variable, it can be written as follows.

let obj = {
  p: [
    'Hello',
    { y: 'World'}};let { p, p: [x, { y }] } = obj;
x // "Hello"
y // "World"
p // ["Hello", {y: "World"}]Copy the code

The deconstruction of an object can also specify default values.

var {x = 3} = {};
x // 3

var {x, y = 5} = {x: 1};
x // 1
y // 5

var {x: y = 3} = {};
y // 3

var {x: y = 3} = {x: 5};
y // 5

var { message: msg = 'Something went wrong' } = {};
msg // "Something went wrong"
Copy the code

The default is valid only if the object’s attribute value is strictly equal to undefined.

var {x = 3} = {x: undefined};
x // 3

var {x = 3} = {x: null};
x // null
Copy the code

In the code above, the attribute x is null, and since null is not exactly equal to undefined, it is a valid assignment, causing the default value 3 not to take effect.

String deconstruction

1. Strings can also be destructively assigned. This is because at this point, the string is converted to an array-like object.

const [a, b, c, d, e] = 'hello';
a // "h"
b // "e"
c // "l"
d // "l"
e // "o"
Copy the code

Array-like objects have a length attribute, so you can also deconstruct and assign to this attribute.

let {length : len} = 'hello';
len // 5Copy the code

Fourth, use

Destructive assignment of variables has many uses.

(1) Exchange the values of variables

let x = 1;
let y = 2;

[x, y] = [y, x];
Copy the code

The above code exchanges the values of variables X and y, which is not only concise, but also easy to read, semantic very clear.

(2) Return multiple values from the function

A function can return only one value. If you want to return multiple values, you must return them in arrays or objects. With deconstructing assignments, it’s very convenient to pull out these values.

// Return an arrayfunction example() {
  return [1, 2, 3];
}
let[a, b, c] = example(); // Return an objectfunction example() {
  return {
    foo: 1,
    bar: 2
  };
}
let { foo, bar } = example();
Copy the code

(3) Definition of function parameters

Destructuring assignments makes it easy to map a set of parameters to variable names.

// Arguments are an ordered set of valuesfunctionf([x, y, z]) { ... } f([1, 2, 3]); // Arguments are an unordered set of valuesfunction f({x, y, z}) { ... }
f({z: 3, y: 2, x: 1});
Copy the code

(4) Extract JSON data

Destructuring assignments is especially useful for extracting data from JSON objects.

let jsonData = {
  id: 42,
  status: "OK",
  data: [867, 5309]
};

let{ id, status, data: number } = jsonData; console.log(id, status, number); / / 42,"OK", [867, 5309]
Copy the code

The code above can quickly extract the value of the JSON data.

(5) Default values of function parameters

jQuery.ajax = function (url, {
  async = true,
  beforeSend = function () {},
  cache = true,
  complete = function () {},
  crossDomain = false,
  global = true,
  // ... more config
} = {}) {
  // ... do stuff
};
Copy the code

The default value of the specified argument, avoids the within the function body to write var foo = config. The foo | | ‘default foo’; Statement like this.

(6) Traverse the Map structure

You can use for any object that has the Iterator interface deployed. The of loop traverses. The Map structure natively supports the Iterator interface, and it is very convenient to obtain key names and values along with the deconstructive assignment of variables.

const map = new Map();
map.set('first'.'hello');
map.set('second'.'world');

for (let [key, value] of map) {
  console.log(key + " is " + value);
}
// first is hello
// second is world
Copy the code

If you just want to get the key name, or if you just want to get the key value, you can write it like this.

// Get the key namefor (let[key] of map) { // ... } // get the key valuefor (let [,value] of map) {
  // ...
}
Copy the code

(7) The specified method of input module

When a module is loaded, it is often necessary to specify which methods to input. Deconstructing assignment makes the input statement very clear.

const { SourceMapConsumer, SourceNode } = require("source-map");Copy the code

5. String extension

String traverser interface

ES6 adds a traverser interface for strings (see chapter Iterator), which allows strings to be used for… The of loop traverses.

for (let codePoint of 'foo') {
  console.log(codePoint)
}
// "f"
// "o"
// "o"Copy the code

2. Template string

You can also call functions from a template string.

function fn() {
  return "Hello World";
}

`foo ${fn()} bar`
// foo Hello World bar
Copy the code

If the value in braces is not a string, it is converted to a string according to the usual rules. For example, if the braces are an object, the toString method of the object will be called by default.

6. New string method

Example methods: includes(), startsWith(), endsWith()

Traditionally, JavaScript has only had the indexOf method, which can be used to determine whether one string is contained within another. ES6 offers three new approaches.

  • Includes () : Returns a Boolean value indicating whether the parameter string was found.
  • StartsWith () : Returns a Boolean value indicating whether the argument string is at the head of the original string.
  • EndsWith () : Returns a Boolean value indicating whether the argument string is at the end of the original string.
let s = 'Hello world! ';

s.startsWith('Hello') / /true
s.endsWith('! ') / /true
s.includes('o') / /true
Copy the code

All three of these methods support a second parameter indicating where the search begins.

let s = 'Hello world! ';

s.startsWith('world'/ /, 6)true
s.endsWith('Hello'/ /, 5)true
s.includes('Hello'/ /, 6)false
Copy the code

The code above shows that with the second argument n, endsWith behaves differently from the other two methods. It works for the first n characters, while the other two work from the NTH position up to the end of the string.

Example method: repeat()

The repeat method returns a new string, repeating the original string n times.

'x'.repeat(3) // "xxx"
'hello'.repeat(2) // "hellohello"
'na'.repeat(0) // ""
Copy the code

The argument is rounded if it is a decimal.

'na'. Repeat (2.9) / /"nana"
Copy the code

If the repeat argument is negative or Infinity, an error is reported.

'na'.repeat(Infinity)
// RangeError
'na'.repeat(-1)
// RangeError
Copy the code

However, if the argument is a decimal between 0 and -1, it is the same as 0 because the round is performed first. If the decimal number between 0 and -1 is rounded to -0, repeat is regarded as 0.

'na'. Repeat (0.9) / /""
Copy the code

The NaN argument is equal to 0.

'na'.repeat(NaN) // ""
Copy the code

If the argument of the repeat is a string, it is converted to a number first.

'na'.repeat('na') / /""
'na'.repeat('3') / /"nanana"Copy the code

7. Function extensions

Rest parameters

ES6 introduces the REST parameter (of the form… Variable name), used to get extra arguments to a function so you don’t need to use the arguments object. The rest argument goes with a variable that puts the extra arguments into an array.

functionadd(... values) {let sum = 0;

  for (var val of values) {
    sum += val;
  }

  return sum;
}

add(2, 5, 3) // 10
Copy the code

The add function in the above code is a summation function that can be passed any number of arguments using the REST argument.

Arrow function

The arrow function has several uses with caution.

(1) The this object inside the function is the object at which it is defined, not the object at which it is used.

(2) Cannot be used as a constructor, that is, cannot use the new command, otherwise an error will be thrown.

(3) You can’t use arguments. This object does not exist in the function body. If you do, use the REST argument instead.

(4) Yield cannot be used, so arrow functions cannot be used as Generator functions.

The first of these four points is particularly noteworthy. The point of this object is mutable, but in arrow functions, it is fixed.

function foo() {
  setTimeout(() => {
    console.log('id:', this.id);
  }, 100);
}

var id = 21;

foo.call({ id: 42 });
// id: 42
Copy the code

In the code above, the argument to setTimeout is an arrow function that takes effect when foo is generated and will not execute until 100 milliseconds later. If it were a normal function, this would point to the global object Window and print 21. However, the arrow function causes this to always point to the object where the function definition is in effect ({id: 42} in this case), so the output is 42.

8. Array extensions

1.Array.from()

The array. from method is used to convert two types of objects into true arrays: array-like objects and iterable objects (including the new ES6 data structures Set and Map).

Here is an array-like object, array. from turns it into a real Array.

let arrayLike = {
    '0': 'a'.'1': 'b'.'2': 'c', length: 3 }; Var arr1 = [].slice.call(arrayLike); / / /'a'.'b'.'c'] // ES6letarr2 = Array.from(arrayLike); / / /'a'.'b'.'c']Copy the code

2.Array.of()

The array. of method converts a set of values to an Array.

Array of (3, 11, 8) / /,11,8 [3] Array of (3) / / [3] Array) of (3). The length / / 1Copy the code

3. Find () and findIndex() of array instances

The find method of an array instance, used to find the first array member that matches the criteria. Its argument is a callback function that is executed by all array members until the first member that returns true is found, and then returned. If there is no qualified member, undefined is returned.

4. Array instance fill()

The fill method fills an array with the given value.

['a'.'b'.'c'].fill(7)
// [7, 7, 7]

new Array(3).fill(7)
// [7, 7, 7]
Copy the code

As the code above shows, the fill method is handy for initializing an empty array. Any existing elements in the array will be erased.

5. Array instance entries(), keys() and values()

ES6 provides three new methods — entries(), keys() and values() — for traversing groups of numbers. They both return an Iterator object (see the Chapter on Iterator), which can be used as a for… The of loop is traversed, the only differences being that keys() is traversal of key names, values() is traversal of key values, and entries() is traversal of key value pairs.

Array instance flat(), flatMap()

The members of arrays are sometimes still arrays, array.prototype.flat () is used to “flatten” nested arrays into one-dimensional arrays. This method returns a new array with no effect on the original data.

[1, 2, [3, 4]].flat()
// [1, 2, 3, 4]
Copy the code

In the code above, the flat() method takes the members of the subarray and adds them in their original places.

Flat () “flattens” only one layer by default. If you want to “flatten” a nested array of multiple layers, you can write the argument to the flat() method as an integer representing the number of layers you want to flatten, which defaults to 1.

[1, 2, [3, [4, 5]]].flat()
// [1, 2, 3, [4, 5]]

[1, 2, [3, [4, 5]]].flat(2)
// [1, 2, 3, 4, 5]
Copy the code

In the code above, flat() takes 2 to “flatten” the two layers of nested arrays.

If you want to convert to a one-dimensional array regardless of the number of nesting levels, you can use the Infinity keyword as an argument.

[1, [2, [3]]].flat(Infinity)
// [1, 2, 3]
Copy the code

If the array is empty, the flat() method skips the empty.

[1, 2, , 4, 5].flat()
// [1, 2, 4, 5]
Copy the code

The flatMap() method performs a function on each member of the original Array (equivalent to array.prototype.map ()), and then flat() on the Array of returned values. This method returns a new array, leaving the original array unchanged.

/ / equivalent to the [[2, 4], [3, 6], [4, 8]]. Flat () [2, 3, 4] flatMap ((x) = > [x, x * 2]) / / [2, 4, 3, 6, 4, 8]Copy the code

FlatMap () can expand only one layer array.

/ / equivalent to the [[[2]], [[4]], [[6]], [[8]]]. Flat () [1, 2, 3, 4]. FlatMap (x = > [* 2] [x]) / / [[2], [4], [6], [8]]Copy the code

In the above code, the traversal function returns a two-layer array, but by default only one layer can be expanded, so flatMap() still returns a nested array.

The flatMap() method takes a traversal function that takes three arguments: the current array member, the position of the current array member (starting from zero), and the original array.

arr.flatMap(function callback(currentValue[, index[, array]]) {
  // ...
}[, thisArg])
Copy the code

The flatMap() method can also have a second parameter that binds this to the traversal function

9. Extension of objects

A concise representation of attributes

The CommonJS module outputs a set of variables and is a good place to write succinct.

let ms = {};

function getItem (key) {
  return key in ms ? ms[key] : null;
}

function setItem (key, value) {
  ms[key] = value;
}

function clear () {
  ms = {};
}

module.exports = { getItem, setItem, clear }; Module. exports = {getItem: getItem,setItem: setItem,
  clear: clear
};Copy the code

Second, super keyword

We know that the this keyword always refers to the current object of the function, but ES6 has added another similar keyword super to refer to the prototype object of the current object.

const proto = {
  foo: 'hello'
};

const obj = {
  foo: 'world'.find() {
    returnsuper.foo; }}; Object.setPrototypeOf(obj, proto); obj.find() //"hello"
Copy the code

In the code above, the object obj.find() method references the foo property of the prototype object proto via super.foo.

Note that when the super keyword represents a prototype object, it can only be used in the object’s methods, and will return an error if used elsewhere.

const o = Object.create({ x: 1, y: 2 });
o.z = 3;

let{ x, ... newObj } = o;let { y, z } = newObj;
x // 1
y // undefined
z // 3
Copy the code

In the above code, variable X is a pure deconstructed assignment, so it can read the properties inherited from object O; Variables y and z are deconstructed assignments of the extended operators. They can only read the attributes of the object O itself, so the assignment of variable Z can be successful, and variable Y cannot take a value

The extension operator can be used to merge two objects.

letab = { ... a, ... b }; / / is equivalent tolet ab = Object.assign({}, a, b);Copy the code

10. Add methods to objects

First, the Object. The assign ()

Basic usage

The Object.assign method is used to merge objects. It copies all the enumerable attributes of the source Object to the target Object.

const target = { a: 1 };

const source1 = { b: 2 };
const source2 = { c: 3 };

Object.assign(target, source1, source2);
target // {a:1, b:2, c:3}Copy the code

Pay attention to the point

(1) Shallow copy

The object. assign method implements shallow copy, not deep copy. That is, if the value of an attribute of the source object is an object, the target object copies a reference to that object.

const obj1 = {a: {b: 1}};
const obj2 = Object.assign({}, obj1);

obj1.a.b = 2;
obj2.a.b // 2
Copy the code

In the code above, the a attribute of the source Object obj1 is an Object. Copy object. assign is a reference to this Object. Any changes to this object are reflected on the target object.

(2) The replacement of the same attribute

For nested objects, once you encounter a property of the same name, object. assign is handled by replacing rather than adding.

const target = { a: { b: 'c', d: 'e' } }
const source = { a: { b: 'hello' } }
Object.assign(target, source)
// { a: { b: 'hello'}}Copy the code

{a: {b: ‘hello’, d: ‘e’}} the target attribute is replaced entirely by the source attribute. This is usually not what developers want and needs to be very careful.

Some libraries offer custom versions of Object.assign (such as Lodash’s _. DefaultsDeep method), which allow for deep-copy merging.

(3) Array processing

Object.assign works with arrays, but treats arrays as objects.

Object.assign([1, 2, 3], [4, 5])
// [4, 5, 3]
Copy the code

In the above code, object. assign treats the array as objects with attributes 0, 1, and 2, so attribute 4 0 of the source array overwrites attribute 1 0 of the target array.

(4) The processing of value functions

Object.assign can only be used to copy values. If the value to be copied is a value function, it will be evaluated and then copied.

const source = {
  get foo() { return1}}; const target = {}; Object.assign(target,source)
// { foo: 1 }
Copy the code

In the code above, the foo attribute of the source Object is an evaluator. Object.assign does not copy this evaluator.

Second, the Object. The keys ()

ES5 introduced the Object.keys method, which returns an array of all the key names of the parameter Object’s own (not inherited) Enumerable properties.

var obj = { foo: 'bar', baz: 42 };
Object.keys(obj)
// ["foo"."baz"]
Copy the code

ES2017 introduced object. values and object. entries with Object.keys as complementary means of traversing an Object for… Of recycling.

let {keys, values, entries} = Object;
let obj = { a: 1, b: 2, c: 3 };

for (let key of keys(obj)) {
  console.log(key); // 'a'.'b'.'c'
}

for (letvalue of values(obj)) { console.log(value); // 1, 2, 3}for (let[key, value] of entries(obj)) { console.log([key, value]); / / /'a', 1], ['b', 2], ['c', 3)}Copy the code

11. The Set and Map

A, the Set

The Set itself is a constructor used to generate the Set data structure.

const s = new Set();

[2, 3, 5, 4, 5, 2, 2].forEach(x => s.add(x));

for (let i of s) {
  console.log(i);
}
// 2 3 5 4
Copy the code

The code above adds members to the Set structure using the add() method, which shows that the Set structure does not add duplicate values.

Properties and methods of a Set instance

Instances of the Set structure have the following properties.

  • Set.prototype.constructorThe: constructor is the defaultSetFunction.
  • Set.prototype.sizeReturns theSetThe total number of members of an instance.

The methods of a Set instance fall into two broad categories: operation methods (for manipulating data) and traversal methods (for traversing members). The following four operations are introduced.

  • Set.prototype.add(value): adds a value and returns the Set structure itself.
  • Set.prototype.delete(value): Deletes a value and returns a Boolean value indicating whether the deletion was successful.
  • Set.prototype.has(value): Returns a Boolean value indicating whether the value isSetA member of.
  • Set.prototype.clear(): Clears all members with no return value.

Traversal operation

An instance of a Set structure has four traversal methods that can be used to traverse members.

  • Set.prototype.keys(): returns a traverser for key names
  • Set.prototype.values(): returns a traverser for key values
  • Set.prototype.entries(): returns a traverser for key-value pairs
  • Set.prototype.forEach(): Iterates through each member using the callback function

Second, the WeakSet

meaning

WeakSet structure is similar to Set, which is also a collection of non-repeating values. However, it differs from Set in two ways.

First, WeakSet members can only be objects, not other types of values. Secondly, the objects in WeakSet are weak references, that is, garbage collection mechanism does not consider WeakSet’s reference to the object, that is to say, if other objects no longer reference the object, then garbage collection mechanism will automatically recover the memory occupied by the object, not considering the object still exists in WeakSet.

Third, the Map

Meaning and basic usage

JavaScript objects are essentially collections of key-value pairs (Hash structures), but traditionally strings can only be used as keys. This puts a great limit on its use. ES6 provides Map data structures. It is a collection of key-value pairs similar to objects, but the range of “keys” is not limited to strings. Values of all types (including objects) can be used as keys. In other words, the Object structure provides string-value mapping, and the Map structure provides value-value mapping, which is a more complete Hash structure implementation.

Note that only references to the same object are treated as the same key by the Map structure. Be very careful about this.

const map = new Map();

map.set(['a'], 555);
map.get(['a']) // undefined
Copy the code

The set and GET methods in the above code appear to be for the same key, but in fact they are two different array instances, the memory address is different, so the get method cannot read the key, return undefined.

Four, WeakMap

meaning

WeakMap structure is similar to Map structure and is also used to generate a set of key-value pairs.

WeakMap can be usedsetConst wm1 = new WeakMap(); const key = {foo: 1}; wm1.set(key, 2); Wm1.get (key) // 2 // WeakMap can also accept an array, // as a constructor parameter const k1 = [1, 2, 3]; const k2 = [4, 5, 6]; const wm2 = new WeakMap([[k1,'foo'], [k2, 'bar']]);
wm2.get(k2) // "bar"
Copy the code

WeakMap differs from Map in two ways.

First, WeakMap only accepts objects as key names (except null) and does not accept other types of values as key names.

Second, the object to which the key name of WeakMap points is not included in the garbage collection mechanism.

12.Proxy

An overview,

Proxy is used to modify the default behavior of certain operations, which is equivalent to making changes at the language level. Therefore, it is a kind of “meta programming”, that is, programming a programming language.

Proxy can be understood as a layer of “interception” before the target object. All external access to the object must pass this layer of interception. Therefore, Proxy provides a mechanism for filtering and rewriting external access. The word Proxy is used to mean that it acts as a Proxy for certain operations.

var obj = new Proxy({}, {
  get: function (target, key, receiver) {
    console.log(`getting ${key}! `);return Reflect.get(target, key, receiver);
  },
  set: function (target, key, value, receiver) {
    console.log(`setting ${key}! `);returnReflect.set(target, key, value, receiver); }});Copy the code

The code above sets up a layer of interception on an empty object, redefining the read (get) and set (set) behavior of the property. I won’t explain the syntax here, but just look at the results. To read and write properties of obj, the object with interception behavior set, the following result is obtained.

obj.count = 1 // setting count! ++obj.count // getting count! // setting count! / / 2Copy the code

The code above shows that the Proxy actually overrides the point operator by overwriting the original definition of the language with its own definition.

13.Promise

I. Promise objects have the following two characteristics.

(1) The state of the object is not affected by the outside world. The Promise object represents an asynchronous operation with three states: Pending, fulfilled and Rejected. Only the result of an asynchronous operation can determine the current state, and no other operation can change the state. That’s where the name “Promise” comes from. Its English name means “Promise,” indicating that nothing else can change it.

(2) Once the state changes, it will never change again, and this result can be obtained at any time. There are only two possibilities for the state of the Promise object to change from pending to depressing and from pending to Rejected. As long as these two things are happening the state is fixed, it’s not going to change, it’s going to stay the same and that’s called resolved. If the change has already occurred, you can add a callback to the Promise object and get the same result immediately. This is quite different from an Event, which has the characteristic that if you miss it and listen again, you will not get the result.

Note that in order to facilitate the writing, the “resolved” in this chapter only refers to the regrettable state, excluding the rejected state.

With the Promise object, asynchronous operations can be expressed as a flow of synchronous operations, avoiding layers of nested callback functions. In addition, Promise objects provide a unified interface that makes it easier to control asynchronous operations.

Promise also has some downsides. First, there is no way to cancel a Promise; once it is created, it is executed immediately and cannot be cancelled halfway through. Second, if you don’t set a callback function, errors thrown inside a Promise won’t be reflected externally. Third, when you are in a pending state, you have no way of knowing what stage of progress you are currently in (just beginning or just finishing).

Second, the Promise. Prototype. Then ()

Promise instances have THEN methods, that is, then methods defined on the prototype object Promise.Prototype. It adds a callback function to the Promise instance when the state changes. As mentioned earlier, the first argument to the THEN method is the callback in the Resolved state and the second argument (optional) is the callback in the Rejected state.

Third, the Promise. Prototype. Catch ()

The promise.prototype. Catch method is an alias for. Then (null, Rejection) or. Then (undefined, Rejection) to specify a callback when an error occurs.

Four, Promise. Prototype. Finally ()

The finally method is used to specify actions that will be performed regardless of the final state of the Promise object. This method was introduced as a standard in ES2018.

Fifth, Promise. All ()

The promise.all method is used to wrap multiple Promise instances into a new Promise instance.

const p = Promise.all([p1, p2, p3]);
Copy the code

In the code above, the promise. all method takes an array of parameters. P1, P2, and p3 are all Promise instances. If they are not, the Promise. (The promise. all method can take arguments that are not arrays, but must have an Iterator interface and return each member as a Promise instance.)

The state of P is determined by P1, P2 and P3, which can be divided into two cases.

(1) Only when the states of P1, P2 and P3 become depressing, the state of P will become depressing. At this time, the return values of P1, P2 and P3 will form an array and be passed to the callback function of P.

(2) As long as p1, P2 and P3 are rejected, P becomes rejected, and the return value of the first rejected instance is passed to p’s callback function.

Here is a concrete example.

// Generate an array of Promise objects const Promises = [2, 3, 5, 7, 11, 13]. Map (function (id) {
  return getJSON('/post/' + id + ".json");
});

Promise.all(promises).then(function (posts) {
  // ...
}).catch(function(reason){
  // ...
});
Copy the code

Promises are an array of six Promise instances. Only when the states of the six Promise instances become fulfilled or one of them becomes Rejected, will the callback function behind Promise.

Six, Promise. Race ()

The promise.race method also wraps multiple Promise instances into a new Promise instance.

const p = Promise.race([p1, p2, p3]);
Copy the code

In the above code, the state of P changes as long as one of the first instances of P1, P2, and P3 changes state. The return value of the first changed Promise instance is passed to p’s callback.

The parameters of the promise.race method are the same as those of the promise.all method. If it is not a Promise instance, the promise.resolve method, described below, is first called to convert the parameters to a Promise instance, and then further processing.

Here’s an example that changes the state of a Promise to Reject if no results are available within a specified time, or resolve otherwise.

const p = Promise.race([
  fetch('/resource-that-may-take-a-while'),
  new Promise(function (resolve, reject) {
    setTimeout(() => reject(new Error('request timeout')), 5000)})]); p .then(console.log) .catch(console.error);Copy the code

In the code above, if the fetch method fails to return a result within 5 seconds, the status of the variable P changes to Rejected, which triggers the callback specified by the catch method.

14. The Iterator and for… Of circulation

The concept of an Iterator

In ES6, Map and Set are added to JavaScript’s original data structures that represent “collections”, mainly arrays and objects. There are four data sets, and users can combine them to define their own data structures, such as arrays whose members are Maps and maps whose members are objects. This requires a unified interface mechanism to handle all the different data structures.

An Iterator is one such mechanism. It is an interface that provides a unified access mechanism for various data structures. The Iterator interface can be deployed on any data structure to complete traversal (that is, processing all members of the data structure in turn).

Iterator provides a unified and simple interface for accessing various data structures. Second, the members of the data structure can be arranged in a certain order. Third, ES6 created a new traversal command for… The of loop is an Iterator interface for… Of consumption.

The Iterator traverses like this.

(1) Create a pointer object that points to the starting position of the current data structure. That is, the traverser object is essentially a pointer object.

(2) The first call to the next method of the pointer object can point to the first member of the data structure.

(3) The next call to the pointer object points to the second member of the data structure.

(4) Keep calling the next method of the pointer object until it points to the end of the data structure.

Each time the next method is called, information about the current member of the data structure is returned. Specifically, it returns an object containing both the value and done attributes. Where, the value attribute is the value of the current member, and the done attribute is a Boolean value indicating whether the traversal is complete.

Situations in which the Iterator interface is called

There are a few situations where the Iterator interface (symbol. Iterator method) is called by default, except for the for… Of loops, and a couple of other occasions.

(1) Deconstructing assignment

The symbol. iterator method is called by default when destructuring arrays and Set structures.

let set = new Set().add('a').add('b').add('c');

let [x,y] = set;
// x='a'; y='b'

let [first, ...rest] = set;
// first='a'; rest=['b'.'c'];
Copy the code

(2) Extended operators

Extended operators (…) The default Iterator interface is also called.

Var STR = //'hello';
[...str] //  ['h'.'e'.'l'.'l'.'o'/ / two caseslet arr = ['b'.'c'];
['a'. arr,'d'] / ['a'.'b'.'c'.'d']
Copy the code

The extension operator of the above code internally calls the Iterator interface.

In effect, this provides an easy mechanism to turn any data structure with the Iterator interface deployed into an array. That is, whenever a data structure has an Iterator interface deployed, you can use the extension operator on it to turn it into an array.

let arr = [...iterable];
Copy the code

(3) the yield *

Yield * is followed by a traversable structure, which invokes the traverser interface of that structure.

let generator = function* () { yield 1; Yield * (2 and 4]; yield 5; }; var iterator = generator(); iterator.next() // { value: 1,done: false }
iterator.next() // { value: 2, done: false }
iterator.next() // { value: 3, done: false }
iterator.next() // { value: 4, done: false }
iterator.next() // { value: 5, done: false }
iterator.next() // { value: undefined, done: true }
Copy the code

(4) Other occasions

Because array traversal calls the traverser interface, any situation that takes an array as an argument actually calls the traverser interface. Here are some examples.

  • for… of
  • Array.from()
  • WeakMap(), Set(), WeakMap(), WeakSet() (e.gnew Map([['a',1],['b',2]]))
  • Promise.all()
  • Promise.race()

Comparison with other traversal grammars

In the case of arrays, JavaScript provides multiple traversal syntax. The original way to write it is the for loop.

for (var index = 0; index < myArray.length; index++) {
  console.log(myArray[index]);
}
Copy the code

This is cumbersome, so arrays provide built-in forEach methods.

myArray.forEach(function (value) {
  console.log(value);
});
Copy the code

The problem with this is that you can’t break out of the forEach loop, and neither break nor return will work.

for… The in loop iterates through the key names of a number group.

for (var index in myArray) {
  console.log(myArray[index]);
}
Copy the code

for… The in loop has several disadvantages.

  • The array’s key name is a number, butfor... inLoops are strings with keys “0”, “1”, “2”, and so on.
  • for... inThe loop iterates not only over numeric key names, but also over other manually added keys, and even keys on the prototype chain.
  • In some cases,for... inThe loop iterates over the key names in any order.

All in all, for… The in loop is designed primarily for traversing objects, not for traversing groups of numbers.

for… The of loop has some significant advantages over the above approaches.

for (let value of myArray) {
  console.log(value);
}
Copy the code
  • With withfor... inSame concise syntax, but nofor... inThose flaws.
  • Different from theforEachMethod, it can be withbreak,continueandreturnUse together.
  • Provides a unified operation interface for traversing all data structures.

Here is a statement that uses the break statement to break for… An example of an of loop.

for (var n of fibonacci) {
  if (n > 1000)
    break;
  console.log(n);
}
Copy the code

The example above prints Fibonacci numbers less than or equal to 1000. If the current item is greater than 1000, the for… statement is broken. Of circulation.

15. Syntax of generator functions


1. Basic Concepts

Generator functions are an asynchronous programming solution provided by ES6 with completely different syntactic behavior from traditional functions. This chapter describes the syntax and API of Generator functions in detail. For asynchronous programming applications, see The chapter “Asynchronous Applications of Generator Functions”.

Generator functions can be understood in many ways. Syntactically, the Generator function is a state machine that encapsulates multiple internal states.

Execution of Generator returns an traverser object, that is, Generator is also a traverser object Generator in addition to the state machine. A traverser object that iterates through each state within the Generator in turn.

2. Relationship with Iterator

Iterator the symbol. iterator method of any object equal to the iterator generator function of that object. Calling this function returns an iterator object for that object.

Since a Generator function is an iterator Generator function, you can assign Generator to the Symbol. Iterator property of an object so that the object has an Iterator interface.

var myIterable = {};
myIterable[Symbol.iterator] = function* () {
  yield 1;
  yield 2;
  yield 3;
};

[...myIterable] // [1, 2, 3]
Copy the code

In the code above, the Generator function assigns the Symbol. Iterator property, thus giving the myIterable object an Iterator interface that can be… Operator traversal.

3.for… Of circulation

for… The of loop automatically iterates over the Iterator generated when the Generator function is running, and there is no need to call the next method.

function* foo() {
  yield 1;
  yield 2;
  yield 3;
  yield 4;
  yield 5;
  return 6;
}

for (let v of foo()) {
  console.log(v);
}
// 1 2 3 4 5
Copy the code

The code above uses for… The of loop displays the values of the five yield expressions in turn. Note here that once the next method returns an object with the done attribute true, for… The of loop terminates and does not contain the return object, so the 6 returned by the return statement above is not included in the for… In the loop of.

4. Similarities between next(), throw() and return()

Next (), throw(), and return() are essentially the same thing and can be understood together. Each of these functions allows the Generator function to resume execution and replaces the yield expression with a different statement.

Next () replaces the yield expression with a value.

Throw () replaces the yield expression with a throw statement.

Return () replaces the yield expression with a return statement.

5.Generator and state machine

The Generator is the best structure for implementing a state machine. For example, the clock function below is a state machine.

var ticking = true;
var clock = function() {
  if (ticking)
    console.log('Tick! ');
  else
    console.log('Tock! '); ticking = ! ticking; }Copy the code

The clock function in the above code has two states (Tick and Tock) that change each time it is run. This function, if implemented with a Generator, looks like this.

var clock = function* () {
  while (true) {
    console.log('Tick! ');
    yield;
    console.log('Tock! '); yield; }};Copy the code

Compared with the ES5 implementation, the Generator implementation above has fewer external variables to tick the state, which makes it cleaner, more secure (the state is not tampered with illegally), more functional in nature and more elegant in writing. The Generator can hold state without external variables because it contains state information, namely whether or not it is currently paused.

16. Async function

A meaning,

The ES2017 standard introduces async functions to make asynchronous operations more convenient.

What is async function? In short, it is the syntactic sugar of Generator functions.

There is a Generator function that reads two files in turn.

const fs = require('fs');

const readFile = function (fileName) {
  return new Promise(function (resolve, reject) {
    fs.readFile(fileName, function(error, data) {
      if (error) return reject(error);
      resolve(data);
    });
  });
};

const gen = function* () {
  const f1 = yield readFile('/etc/fstab');
  const f2 = yield readFile('/etc/shells');
  console.log(f1.toString());
  console.log(f2.toString());
};
Copy the code

The function gen in the above code can be written as async, as follows.

const asyncReadFile = async function () {
  const f1 = await readFile('/etc/fstab');
  const f2 = await readFile('/etc/shells');
  console.log(f1.toString());
  console.log(f2.toString());
};Copy the code

The improvements of async over Generator are shown in the following four aspects.

(1) Built-in actuators.

Generator functions must be executed by an executor, hence the CO module, while async functions have their own executor. In other words, async functions are executed exactly like normal functions, with only one line.

(2) Better semantics.

Async and await are semantic clearer than asterisks and yield. Async means that there is an asynchronous operation in a function, and await means that the following expression needs to wait for the result.

(3) wider applicability.

According to the CO module convention, yield can only be followed by Thunk or Promise, while async can be followed by await and Promise and primitive type values (numeric, string, Boolean, etc.). But that automatically changes to an immediate Resolved Promise).

(4) Return the Promise.

Async functions return a Promise object, which is much more convenient than Generator functions returning an Iterator. You can specify what to do next using the then method.

Further, async functions can be thought of as multiple asynchronous operations wrapped as a Promise object, and await commands are syntactic sugar for internal THEN commands.

Async functions can be used in many forms.

// Function declaration asyncfunction foo() {// Function expression const foo = asyncfunction() {}; // Object methodlet obj = { async foo() {}}; obj.foo().then(...) // Class Storage {constructor() {
    this.cachePromise = caches.open('avatars');
  }

  async getAvatar(name) {
    const cache = await this.cachePromise;
    return cache.match(`/avatars/${name}.jpg`);
  }
}

const storage = new Storage();
storage.getAvatar('jake'). Then (...). ; // Arrow function const foo = async () => {};Copy the code

Third, await

Sometimes we want to not interrupt subsequent asynchronous operations even if the previous one fails. We can then put the first await in a try… Inside the catch structure, so that the second await is executed regardless of whether the asynchronous operation succeeds or not.

async function f() {
  try {
    await Promise.reject('Wrong');
  } catch(e) {
  }
  return await Promise.resolve('hello world');
}

f()
.then(v => console.log(v))
// hello world
Copy the code

The alternative is to await a Promise object followed by a catch method to handle any errors that may occur earlier.

async function f() {
  await Promise.reject('Wrong')
    .catch(e => console.log(e));
  return await Promise.resolve('hello world'); } f().then(v => console.log(v)Copy the code

4. Implementation principle of Async function

Async functions are implemented by wrapping Generator functions and automatic actuators in one function.

async functionfn(args) { // ... } // is equivalent tofunction fn(args) {
  return spawn(function* () {//... }); }Copy the code

17. Basic syntax for Class

1. Constructor method

The constructor method is the default method of the class and is automatically called when an object instance is generated using the new command. A class must have a constructor method; if it is not explicitly defined, an empty constructor method is added by default.

Class Point {}constructor() {}}Copy the code

In the above code, we define an empty class Point, to which the JavaScript engine automatically adds an empty constructor method.

Two, pay attention

(1) Strict mode

Inside classes and modules, the default is strict mode, so there is no need to use strict to specify the runtime mode. As long as your code is written in a class or module, only strict mode is available. Considering that all future code will actually run in modules, ES6 actually upgrades the entire language to strict mode.

(2) There is no promotion

Unlike ES5, there is no hoist for the class.

(3) Name attribute

Because ES6 classes are essentially just a wrapper around ES5 constructors, many of the functions’ features are inherited by Class, including the name attribute.

Static method

A class is the prototype of an instance, and all methods defined in a class are inherited by the instance. If you prefix a method with the static keyword, it means that the method is not inherited by the instance, but is called directly from the class. This is called a “static method”.

class Foo {
  static classMethod() {
    return 'hello';
  }
}

Foo.classMethod() // 'hello'

var foo = new Foo();
foo.classMethod()
// TypeError: foo.classMethod is not a function
Copy the code

In the code above, the static keyword precedes the classMethod method of class Foo, indicating that the method is a static method that can be called directly on class Foo (foo.classmethod ()), not on an instance of class Foo. If a static method is called on an instance, an error is thrown indicating that the method does not exist.

Static attributes

Static properties refer to properties of the Class itself, class.propName, not properties defined on the instance object (this).

class Foo {
}

Foo.prop = 1;
Foo.prop // 1
Copy the code

The above statement defines a static property prop for class Foo.

Currently, this is the only way to do it, because ES6 explicitly states that there are only static methods inside a Class and no static attributes. There is now a proposal that provides static attributes for classes written with the static keyword in front of instance attributes.

Private methods and private properties


Existing solutions


One way to do this is to make a distinction in naming.

Class Widget {// Public method foo (baz) {this._bar(baz); } // private method _bar(baz) {return this.snaf = baz;
  }

  // ...
}Copy the code

Another approach is to simply move private methods out of the module, since all methods inside the module are visible.

class Widget {
  foo (baz) {
    bar.call(this, baz);
  }

  // ...
}

function bar(baz) {
  return this.snaf = baz;
}Copy the code

Another way is to take advantage of the uniqueness of the Symbol value by naming the private method a Symbol value.

const bar = Symbol('bar');
const snaf = Symbol('snaf');

exportDefault class myClass{// Public method foo(baz) {this[bar](baz); } // Private method [bar](baz) {return this[snaf] = baz;
  }

  // ...
};Copy the code

18. The Class inheritance


A list,

Classes can be inherited through the extends keyword, which is much cleaner and more convenient than ES5’s inheritance through modified prototype chains.

class Point {
}

class ColorPoint extends Point {
}
Copy the code

The above code defines a ColorPoint class that inherits all the properties and methods of the Point class through the extends keyword. But since no code is deployed, the two classes are exactly the same, duplicating a Point class

Second, super keyword

The super keyword can be used as either a function or an object. In both cases, it’s used quite differently.

In the first case, super, when called as a function, represents the constructor of the parent class. ES6 requires that the constructor of a subclass must execute the super function once.

class A {}

class B extends A {
  constructor() { super(); }}Copy the code

In the code above, super() in the constructor of subclass B stands for calling the constructor of the parent class. This is necessary, otherwise the JavaScript engine will report an error.

In the second case, super as an object, in a normal method, points to a prototype object of the parent class; In static methods, point to the parent class.

19. The grammar of the Module


An overview,

Historically, JavaScript has not had a module system that allows you to break up a large program into small interdependent files and put them together in a simple way. Other languages have this functionality, such as Ruby’s require, Python’s import, and even CSS has @import, but JavaScript has no support for any of this, which is a huge barrier to developing large, complex projects.

Prior to ES6, there were several module loading schemes developed by the community, the most important being CommonJS and AMD. The former is used for servers and the latter for browsers. ES6 in the language standard level, the realization of module function, and the implementation is quite simple, can completely replace CommonJS and AMD specifications, become a common browser and server module solution.

Import command

If you want to rename the input variable, the import command uses the as keyword to rename the input variable.

import { lastName as surname } from './profile.js';Copy the code

The variables entered by the import command are read-only because it is by nature an input interface. That is, it is not allowed to rewrite interfaces in scripts that load modules.

import {a} from './xxx.js'

a = {}; // Syntax Error : 'a' is read-only;
Copy the code

In the above code, the script loads variable A and reassigns it to an error because A is a read-only interface. However, if A is an object, overwriting a’s properties is allowed.

import {a} from './xxx.js'

a.foo = 'hello'; // It is validCopy the code

Note that the import command is promoted to the top of the module and executed first.

foo();

import { foo } from 'my_module';
Copy the code

The above code does not report an error because import is executed before foo is called. The essence of this behavior is that the import command is executed at compile time, before the code runs.

Run the export default command


Compare the default output with the normal output.

/ / the first groupexport default function crc32() {// output //... } import crc32 from'crc32'; // Input // second groupexport function crc32() {// output //... }; import {crc32} from'crc32'; / / inputCopy the code

The above code is written in two groups. In the first group, when export default is used, the corresponding import statement does not need to use curly braces. The second group is when export default is not used, the corresponding import statement needs to use curly braces.


Fourth, the import

Some applications of import().

(1) Load on demand.

Import () lets you load a module as needed.

button.addEventListener('click', event => {
  import('./dialogBox.js')
  .then(dialogBox => {
    dialogBox.open();
  })
  .catch(error => {
    /* Error handling */
  })
});
Copy the code

In the above code, the import() method is placed in the listener function of the click event, and the module is loaded only when the user clicks the button.

(2) Conditional loading

Import () can be placed in an if code block and loaded with different modules depending on the situation.


(3) Dynamic module path

Import () allows module paths to be generated dynamically.

import(f()) .then(...) ;Copy the code

In the above code, different modules are loaded depending on the result returned by function F.

Pay attention to the point

When import() successfully loads a module, the module is treated as an object as an argument to the then method. Therefore, you can use the syntax of object deconstruction assignment to get the output interface.

import('./myModule.js')
.then(({export1, export2}) => {//... });Copy the code

In the code above, export1 and export2 are both output interfaces to myModule.js and can be destructed.

20. Loading implementation of Module

First, browser loading

By default, browsers load JavaScript scripts synchronously, meaning that the rendering engine stops when it encounters a

<script src="path/to/myModule.js" defer></script>
<script src="path/to/myModule.js" async></script>
Copy the code

In the code above, the

The difference between defer and Async is that defer does not execute until the entire page has been rendered in memory properly (the DOM structure has been fully generated and other scripts have been executed); Once async is finished downloading, the rendering engine interrupts the rendering, executes the script, and continues rendering. In a word, defer means “render before execution” and Async means “download after execution”. Also, if you have multiple defer scripts, they are loaded in the order they appear on the page, which is not guaranteed by multiple Async scripts.

Second, ES6 module and CommonJS module differences

Before discussing Node loading ES6 modules, it is important to understand that ES6 modules are completely different from CommonJS modules.

There are two important differences.

  • The CommonJS module prints a copy of the value, the ES6 module prints a reference to the value.
  • The CommonJS module is run time loaded, and the ES6 module is compile time output interface.

The second difference is because CommonJS loads an object (that is, the module.exports property) that is generated only after the script has run. An ES6 module is not an object, and its external interface is a static definition that is generated during the code static parsing phase.

The first difference is highlighted below.

The CommonJS module outputs a copy of the value, meaning that once a value is output, changes within the module do not affect that value. Take a look at the following example of the module file lib.js.

// lib.js
var counter = 3;
function incCounter() {
  counter++;
}
module.exports = {
  counter: counter,
  incCounter: incCounter,
};
Copy the code

The code above prints the internal variable counter and overwrites the internal method incCounter. Then, load the module in main.js.

// main.js
var mod = require('./lib'); console.log(mod.counter); // 3 mod.incCounter(); console.log(mod.counter); / / 3Copy the code

As the code above shows, once the lib.js module is loaded, its internal changes do not affect the output mod.counter. This is because mod.counter is a primitive value and will be cached. You have to write it as a function to get the internal value.