Coding With Fun
Home Docker Django Node.js Articles Python pip guide FAQ Policy

ES6 deconstructs assignments


May 08, 2021 ES6


Table of contents


1. Deconstructed assignment of the array

Basic usage

ES6 allows variables to be assigned values from arrays and objects in a certain 解构 known as deconstruction.

Previously, variables were assigned values that could only be specified directly.

  1. let a = 1;
  2. let b = 2;
  3. let c = 3;

ES6 allows this to be written below.

  1. let [a, b, c] = [1, 2, 3];

The above code indicates that values can be extracted from the array and assigned to variables according to their corresponding locations.

Essentially, this writing is “模式匹配” which the variable on the left is given a corresponding value as long as the patterns on both sides of the equal sign are the same. Here are some examples of deconstructing using nested arrays.

  1. let [foo, [[bar], baz]] = [1, [[2], 3]];
  2. foo // 1
  3. bar // 2
  4. baz // 3
  5. let [ , , third] = ["foo", "bar", "baz"];
  6. third // "baz"
  7. let [x, , y] = [1, 2, 3];
  8. x // 1
  9. y // 3
  10. let [head, ...tail] = [1, 2, 3, 4];
  11. head // 1
  12. tail // [2, 3, 4]
  13. let [x, y, ...z] = ['a'];
  14. x // "a"
  15. y // undefined
  16. z // []

If deconstruction is unsuccessful, the value of the variable undefined

Another scenario is incomplete 不完全解构 i.e. the pattern to the left of the equal sign matches only a portion of the array to the right of the equal sign. In this case, deconstruction can still succeed.

  1. let [x, y] = [1, 2, 3];
  2. x // 1
  3. y // 2
  4. let [a, [b], d] = [1, [2, 3], 4];
  5. a // 1
  6. b // 2
  7. d // 4

Both of the above examples are incomplete deconstructions, but they work.

If the right side of the equal sign is not an array (or, strictly speaking, not a traversable structure, see chapter Iterator), then an error will be reported.

  1. // 报错
  2. let [foo] = 1;
  3. let [foo] = false;
  4. let [foo] = NaN;
  5. let [foo] = undefined;
  6. let [foo] = null;
  7. let [foo] = {};

The above statements are correct because the value to the right of the equal sign either does not have the Iterator interface (the first five expressions) after the object is converted, or it does not have the Iterator interface itself (the last expression).

For Set you can also use array deconstruction assignments.

  1. let [x, y, z] = new Set(['a', 'b', 'c']);
  2. x // "a"

In fact, as long as a data structure has Iterator it can be deconstructed as an array.

  1. function* fibs() {
  2. let a = 0;
  3. let b = 1;
  4. while (true) {
  5. yield a;
  6. [a, b] = [b, a + b];
  7. }
  8. }
  9. let [first, second, third, fourth, fifth, sixth] = fibs();
  10. sixth // 5

In the code above, fibs is a Generator function (see chapter "Generator Functions"), native to the Iterator interface. Deconstructing assignments gets values from this interface in turn.

The default

解构赋值 allows you to 默认值

  1. let [foo = true] = [];
  2. foo // true
  3. let [x, y = 'b'] = ['a']; // x='a', y='b'
  4. let [x, y = 'b'] = ['a', undefined]; // x='a', y='b'

Note that ES6 uses a strict equal operator internally to determine whether a location has a value. Therefore, the default value takes effect only if an array member is strictly equal to underfined.

  1. let [x = 1] = [undefined];
  2. x // 1
  3. let [x = 1] = [null];
  4. x // null

In the above code, if an array member is null, the default value does not take effect because null is not strictly equal to undefined.

If the default value is an expression, the expression is lazy, i.e. it is evaluated only when it is used.

  1. function f() {
  2. console.log('aaa');
  3. }
  4. let [x = f()] = [1];

In the above code, function f is not executed at all because x can get a value. The above code is actually equivalent to the following code.

  1. let x;
  2. if ([1][0] === undefined) {
  3. x = f();
  4. } else {
  5. x = [1][0];
  6. }

The default value can refer to other variables that deconstruct assignments, but the variable must have been declared.

  1. let [x = 1, y = x] = []; // x=1; y=1
  2. let [x = 1, y = x] = [2]; // x=2; y=2
  3. let [x = 1, y = x] = [1, 2]; // x=1; y=2
  4. let [x = y, y = 1] = []; // ReferenceError: y is not defined

The last expression above reported an error because y had not been declared when x used y as the default.

2. The deconstruction assignment of the object

解构 can be used 数组 but also for 对象

  1. let { foo, bar } = { foo: 'aaa', bar: 'bbb' };
  2. foo // "aaa"
  3. bar // "bbb"

The deconstruction of an object is an important difference from an array. The elements of the array are arranged in 变量 the value of the variable is determined by its position, and the properties of the object are not in order, and the variable must have 属性 same name as the property in order to get the correct value.

  1. let { bar, foo } = { foo: 'aaa', bar: 'bbb' };
  2. foo // "aaa"
  3. bar // "bbb"
  4. let { baz } = { foo: 'aaa', bar: 'bbb' };
  5. baz // undefined

The first example of the code above, the order of the two variables to the left of the equal sign, is inconsistent with the order of the two properties of the same name to the right of the equal sign, but has no effect on the value at all. The variable in the second example does not have a corresponding property of the same name, resulting in a value that is not obtained and is eventually equal to undefined.

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

  1. let {foo} = {bar: 'baz'};
  2. foo // undefined

In the code above, the object to the right of the equal sign does not have a foo property, so the variable foo does not get a value, so it is equal to undefined.

The deconstruction assignment of an object makes it easy to assign methods of an existing object to a variable.

  1. // 例一
  2. let { log, sin, cos } = Math;
  3. // 例二
  4. const { log } = console;
  5. log('hello') // hello

The example of the above code assigns three methods of the Math object to the corresponding variable, which will be much easier to use. Example 2 assigns the .log to the log variable.

If the variable name does not match the property name, it must be written as below.

  1. let { foo: baz } = { foo: 'aaa', bar: 'bbb' };
  2. baz // "aaa"
  3. let obj = { first: 'hello', last: 'world' };
  4. let { first: f, last: l } = obj;
  5. f // 'hello'
  6. l // 'world'

This actually shows that the deconstructed assignment of an object is a short case in the form below (see chapter Extensions of Objects).

  1. let { foo: foo, bar: bar } = { foo: 'aaa', bar: 'bbb' };

That is, the internal mechanism for deconstructing an assignment of an object is to find the property of the same name before assigning it to the corresponding variable. It is the latter, not the former, that is really assigned.

  1. let { foo: baz } = { foo: 'aaa', bar: 'bbb' };
  2. baz // "aaa"
  3. foo // error: foo is not defined

In the code above, foo is the pattern of the match, and baz is the variable. It is the variable baz, not the pattern foo, that is really assigned.

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

  1. let obj = {
  2. p: [
  3. 'Hello',
  4. { y: 'World' }
  5. ]
  6. };
  7. let { p: [x, { y }] } = obj;
  8. x // "Hello"
  9. y // "World"

Note that p is a pattern, not a variable, and is therefore not assigned. If p is also assigned as a variable, it can be written as below.

  1. let obj = {
  2. p: [
  3. 'Hello',
  4. { y: 'World' }
  5. ]
  6. };
  7. let { p, p: [x, { y }] } = obj;
  8. x // "Hello"
  9. y // "World"
  10. p // ["Hello", {y: "World"}]

Here's another example.

  1. const node = {
  2. loc: {
  3. start: {
  4. line: 1,
  5. column: 5
  6. }
  7. }
  8. };
  9. let { loc, loc: { start }, loc: { start: { line }} } = node;
  10. line // 1
  11. loc // Object {start: Object}
  12. start // Object {line: 1, column: 5}

The above code deconstructs the assignment three times, namely, the deconstruction assignment of the loc, start, and line properties. Note that the last deconstruction assignment of line properties, only line is a variable, loc and start are patterns, not variables.

The following is an example of nested assignments.

  1. let obj = {};
  2. let arr = [];
  3. ({ foo: obj.prop, bar: arr[0] } = { foo: 123, bar: true });
  4. obj // {prop:123}
  5. arr // [true]

If the deconstruction pattern is a nested object and the parent property in which the child object is located does not exist, an error will be reported.

  1. // 报错
  2. let {foo: {bar}} = {baz: 'baz'};

In the code above, the foo property of the object to the left of the equal sign corresponds to a sub-object. T he bar property of the sub-object, which is deconstructed with errors. The reason is simple, because foo is equal to undefined at this time, and then take sub-attributes will report errors.

Note that the deconstruction assignment of an object can be taken from inherited properties.

  1. const obj1 = {};
  2. const obj2 = { foo: 'bar' };
  3. Object.setPrototypeOf(obj1, obj2);
  4. const { foo } = obj1;
  5. foo // "bar"

In the code above, the prototype object of object obj1 is obj2. The foo property is not a property of obj1 itself, but a property inherited from obj2, which can be taken from deconstructing assignments.

The default

对象 解构 object can also 默认值

  1. var {x = 3} = {};
  2. x // 3
  3. var {x, y = 5} = {x: 1};
  4. x // 1
  5. y // 5
  6. var {x: y = 3} = {};
  7. y // 3
  8. var {x: y = 3} = {x: 5};
  9. y // 5
  10. var { message: msg = 'Something went wrong' } = {};
  11. msg // "Something went wrong"

The default value takes effect on the condition that the 属性值 object is undefined

  1. var {x = 3} = {x: undefined};
  2. x // 3
  3. var {x = 3} = {x: null};
  4. x // null

In the above code, the property x is equal to null, because null is not strictly equal to undefined, so it is a valid assignment, resulting in the default value 3 will not take effect.

Note the point

(1) If you want to use a declared variable for deconstructing assignments, you must be very careful.

  1. // 错误的写法
  2. let x;
  3. {x} = {x: 1};
  4. // SyntaxError: syntax error

The above code is written incorrectly because the JavaScript engine will understand the code block as a block of code, resulting in a syntax error. You can solve this problem only if you don't write braces at the top of the line and avoid JavaScript interpreting them as blocks of code.

  1. // 正确的写法
  2. let x;
  3. ({x} = {x: 1});

The above code deconstructs the assignment statement and places it in a parenthesis so that it can be executed correctly. See below for the relationship between parentheses and deconstructed assignments.

(2) Deconstructing assignments allows no variable names to be placed in the mode to the left of the equal sign. Therefore, you can write out very odd assignment expressions.

  1. ({} = [true, false]);
  2. ({} = 'abc');
  3. ({} = []);

The above expression is meaningless, but the syntax is legal and can be executed.

(3) Because arrays are special objects in nature, they can be deconstructed by object properties.

  1. let arr = [1, 2, 3];
  2. let {0 : first, [arr.length - 1] : last} = arr;
  3. first // 1
  4. last // 3

The above code deconstructs the array. T he 0 key of the array arr corresponds to the value of 1, and the value of the array arr.length - 1 is 2 keys, and the corresponding value is 3. Square brackets, which are written in the form of "attribute name expressions" (see chapter extended to objects).

3. Deconstructed assignment of strings

Strings can also deconstruct assignments. This is because at this point, the string is converted into an array-like object.

  1. const [a, b, c, d, e] = 'hello';
  2. a // "h"
  3. b // "e"
  4. c // "l"
  5. d // "l"
  6. e // "o"

Array-like objects have a length so you can deconstruct the assignment of that property.

  1. let {length : len} = 'hello';
  2. len // 5

4. Deconstructed assignments for values and boolean values

When deconstructing an assignment, if the equal 右边 is a value and a Boolean value, it is first converted to the 对象

  1. let {toString: s} = 123;
  2. s === Number.prototype.toString // true
  3. let {toString: s} = true;
  4. s === Boolean.prototype.toString // true

In the code above, both the value and the wrapper object of the toString so s can get the value.

The rule for deconstructing assignments is to convert an equal sign to an object or array as long as the value to the right is not an object or array. Because undefined null be converted to objects, deconstructing assignments on them will result in errors.

  1. let { prop: x } = undefined; // TypeError
  2. let { prop: y } = null; // TypeError

5. Deconstructed assignment of function parameters

函数 of 参数 can also be 解构赋值

  1. function add([x, y]){
  2. return x + y;
  3. }
  4. add([1, 2]); // 3

In the above code, the add of the function add are on the surface of an array, but at the moment the arguments are passed in, the array parameters are x y For code inside functions, the parameters they can feel are x and y

Here's another example.

  1. [[1, 2], [3, 4]].map(([a, b]) => a + b);
  2. // [ 3, 7 ]

函数参数 解构 can also use 默认值

  1. function move({x = 0, y = 0} = {}) {
  2. return [x, y];
  3. }
  4. move({x: 3, y: 8}); // [3, 8]
  5. move({x: 3}); // [3, 0]
  6. move({}); // [0, 0]
  7. move(); // [0, 0]

In the above code, the move the function move is an object that is deconstructed to get the x and y If deconstruction fails, x and y equal to the default values.

Note that the following writing will get a different result.

  1. function move({x, y} = { x: 0, y: 0 }) {
  2. return [x, y];
  3. }
  4. move({x: 3, y: 8}); // [3, 8]
  5. move({x: 3}); // [3, undefined]
  6. move({}); // [undefined, undefined]
  7. move(); // [0, 0]

The above code specifies a default value for the parameters of the function move not x and y so you get a different result than the previous one.

undefined the default value of the function argument.

  1. [1, undefined, 3].map((x = 'yes') => x);
  2. // [ 1, 'yes', 3 ]

6. The question of parentheses

Deconstructing assignments is convenient, but it's not easy to parse. For the compiler, whether a pattern is a pattern or an expression is not known from the start, and must be parsed to (or not resolved) equivalents to know.

The question arises as to what to do if parentheses appear in the pattern. ES6's rule is that parentheses should not be used whenever there is ambiguity that could lead to deconstruction.

However, this rule is actually less easily identifiable and quite cumbersome to deal with. Therefore, it is recommended that you do not place parentheses in the pattern whenever possible.

The case in which parentheses cannot be used

The following three deconstruction assignments must not use parentheses.

(1) Variable declaration statement

  1. // 全部报错
  2. let [(a)] = [1];
  3. let {x: (c)} = {};
  4. let ({x: c}) = {};
  5. let {(x: c)} = {};
  6. let {(x): c} = {};
  7. let { o: ({ p: p }) } = { o: { p: 2 } };

The above six statements are error-positive because they are all variable declaration statements and patterns cannot use parentheses.

(2) Function parameters

函数参数 also belong 变量声明 and therefore cannot be parenthesed.

  1. // 报错
  2. function f([(z)]) { return z; }
  3. // 报错
  4. function f([z,(x)]) { return x; }

(3) The pattern of the assignment statement

  1. // 全部报错
  2. ({ p: a }) = { p: 42 };
  3. ([a]) = [5];

The code above places the entire pattern in parentheses, resulting in an error.

  1. // 报错
  2. [({ p: a }), { x: c }] = [{}, {}];

The code above places a portion of the pattern in parentheses, resulting in an error.

You can use parentheses

There is only one case in which you can use parentheses: the non-patterned part of the assignment statement, and you can use parentheses.

  1. [(b)] = [3]; // 正确
  2. ({ p: (d) } = {}); // 正确
  3. [(parseInt.prop)] = [3]; // 正确

The above three lines of statements can be executed correctly because first they are assignment statements, not declaration statements; In the first line statement, the pattern is the first member of the array, p d of parentheses;

7. Use

The deconstruction assignment of variables has many uses.

(1) Exchange the value of the variable

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

The above code exchanges x of the variables x y which are not only concise, but also easy to read, with very clear semantics.

(2) Multiple values are returned from the function

The function can only return one value, and if you want to return more than one value, you can only return them in an array or object. With an understanding of the assignment, it is very convenient to remove these values.

  1. // 返回一个数组
  2. function example() {
  3. return [1, 2, 3];
  4. }
  5. let [a, b, c] = example();
  6. // 返回一个对象
  7. function example() {
  8. return {
  9. foo: 1,
  10. bar: 2
  11. };
  12. }
  13. let { foo, bar } = example();

(3) Definition of function parameters

Deconstructing assignments makes it easy to match a set of parameters to variable names.

  1. // 参数是一组有次序的值
  2. function f([x, y, z]) { ... }
  3. f([1, 2, 3]);
  4. // 参数是一组无次序的值
  5. function f({x, y, z}) { ... }
  6. f({z: 3, y: 2, x: 1});

(4) Extract JSON data

解构赋值 is especially useful for extracting data from JSON objects.

  1. let jsonData = {
  2. id: 42,
  3. status: "OK",
  4. data: [867, 5309]
  5. };
  6. let { id, status, data: number } = jsonData;
  7. console.log(id, status, number);
  8. // 42, "OK", [867, 5309]

The above code quickly extracts the value of the JSON data.

(5) The default value of the function argument

  1. jQuery.ajax = function (url, {
  2. async = true,
  3. beforeSend = function () {},
  4. cache = true,
  5. complete = function () {},
  6. crossDomain = false,
  7. global = true,
  8. // ... more config
  9. } = {}) {
  10. // ... do stuff
  11. };

By specifying the default value of the argument, it is possible to write var foo = config.foo || 'default foo'; Such a statement.

(6) Traversing the Map structure

Any object with an Iterator interface deployed can use for...of traversal. The Map structure natively supports the Iterator interface, which makes it easy to get key names and key values with the deconstruction assignment of variables.

  1. const map = new Map();
  2. map.set('first', 'hello');
  3. map.set('second', 'world');
  4. for (let [key, value] of map) {
  5. console.log(key + " is " + value);
  6. }
  7. // first is hello
  8. // second is world

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

  1. // 获取键名
  2. for (let [key] of map) {
  3. // ...
  4. }
  5. // 获取键值
  6. for (let [,value] of map) {
  7. // ...
  8. }

(7) Enter the specified method of the module

When you load a module, you often need to specify which methods to enter. Deconstructing assignments makes the input statement very clear.

  1. const { SourceMapConsumer, SourceNode } = require("source-map");