es6-变量解构赋值(变量包括:数组,对象,字符串,数值,布尔值,函数),不能使用圆括号的情况和能使用圆括号的情况,解构赋值的使用场景

46 阅读11分钟

一,数组的解构赋值

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

ps:

1....z 使用剩余参数(Rest Parameters)语法,会收集数组剩下的所有元素作为一个新的数组。但是在你的例子中,因为 y 已经消耗了一个值(虽然没有),并且数组中没有更多的元素,因此 z 也会接收到一个空数组 []
2.如果解构不成功,变量的值就等于undefined
let [foo] = [];//foo:undefined,由于空数组 `[]` 没有任何元素,所以在进行解构赋值时,变量 `foo` 将不会接收到任何值,因此 `foo` 的值将是 `undefined`
let [bar, foo] = [1];//foo:undefined
3.如果等号的右边不是数组,那么将会报错。
// 报错
let [foo] = 1;
let [foo] = false;
let [foo] = NaN;
let [foo] = undefined;
let [foo] = null;
let [foo] = {};
4.对于 Set 结构,也可以使用数组的解构赋值。
let [x, y, z] = new Set(['a', 'b', 'c']);
x // "a"

对Set补充

const mySet = new Set(); 
// 添加一些不同的值
mySet.add(1);
mySet.add('hello'); 
mySet.add(true); 
// 尝试添加重复的值 
mySet.add(1);// 重复的值不会被添加 
// 打印 Set 的大小
console.log(mySet.size); // 输出: 3 
// 遍历 Set 
for (let item of mySet) { console.log(item); } 
// 删除一个值 mySet.delete(1);
// 检查某个值是否存在于 Set 中 
console.log(mySet.has('hello')); // 输出: true
5.解构赋值允许指定默认值
let [foo = true] = [];
foo // true

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

ES6 内部使用严格相等运算符(===),判断一个位置是否有值。所以,只有当一个数组成员严格等于undefined,默认值才会生效。

let [x = 1] = [undefined];
x // 1

let [x = 1] = [null];
x // null
6.如果默认值是一个表达式,那么这个表达式是惰性求值的,即只有在用到的时候,才会求值。
function f() {
  console.log('aaa');
}

let [x = f()] = [1];

//等价与
let x;
if ([1][0] === undefined) {
  x = f();
} else {
  x = [1][0];
}

对[1][0]的补充

这个是对单数组的访问,在 [1][0] 的情况下,实际上是直接在表达式内部创建了一个数组,并立即访问其元素:

  1. 创建一个数组

    • [1] 创建了一个包含单个元素 1 的数组。再举例[2][1]这个是对单数组[2],查找第二个元素,这个为undefined
  2. 访问数组的第一个元素

    • [1][0] 访问这个数组的第一个元素,即 1
7.默认值可以引用解构赋值的其他变量,但该变量必须已经声明。
let [x = 1, y = x] = [];     // x=1; y=1
let [x = 1, y = x] = [2];    // x=2; y=2
let [x = 1, y = x] = [1, 2]; // x=1; y=2
let [x = y, y = 1] = [];     // ReferenceError: y is not defined

二,对象的解构赋值

1.对象的解构与数组有一个重要的不同。数组的元素是按次序排列的,变量的取值由它的位置决定;而对象的属性没有次序,变量必须与属性同名,才能取到正确的值。(简单一下就是要赋值的变量必须与要被取值的对象中的属性名相同)
let { foo, bar } = { foo: 'aaa', bar: 'bbb' };
foo // "aaa"
bar // "bbb"

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

let { baz } = { foo: 'aaa', bar: 'bbb' };
baz // undefined
2.对象的解构赋值,可以很方便地将现有对象的方法,赋值到某个变量。
// 例一
let { log, sin, cos } = Math;

// 例二
const { log } = console;
log('hello') // hello
上面代码的例一将`Math`对象的对数、正弦、余弦三个方法,赋值到对应的变量上,使用起来就会方便很多。例二将`console.log`赋值到`log`变量。
3.如果变量名与属性名不一致,必须写成下面这样。
let { foo: baz } = { foo: 'aaa', bar: 'bbb' };
baz // "aaa"

let obj = { first: 'hello', last: 'world' };
let { first: f, last: l } = obj;
f // 'hello'
l // 'world'

//这实际上说明,对象的解构赋值是下面形式的简写
let { foo: foo, bar: bar } = { foo: 'aaa', bar: 'bbb' };

//也就是说,对象的解构赋值的内部机制,是先找到同名属性,然后再赋给对应的变量。真正被赋值的是后者,而不是前者。
let { foo: baz } = { foo: 'aaa', bar: 'bbb' };
baz // "aaa"
foo // error: foo is not defined
//上面代码中,`foo`是匹配的模式,`baz`才是变量。真正被赋值的是变量`baz`,而不是模式`foo`。
4.与数组一样,解构也可以用于嵌套结构的对象。
let obj = {
  p: [
    'Hello',
    { y: 'World' }
  ]
};

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


//例2
let obj = {};
let arr = [];
({ foo: obj.prop, bar: arr[0] } = { foo: 123, bar: true });
obj // {prop:123}
arr // [true]
//步骤:先提取foo,bar的属性值,然后依次赋值给obj.prop,arr[0],所以obj={prop:123} arr输出的是[true](console.log(arr)只写了数组名称就是输出arr整个数组)
5.对象的解构赋值可以取到继承的属性。
const obj1 = {};
const obj2 = { foo: 'bar' };
Object.setPrototypeOf(obj1, obj2);

const { foo } = obj1;
foo // "bar"
//上面代码中,对象`obj1`的原型对象是`obj2`。`foo`属性不是`obj1`自身的属性,而是继承自`obj2`的属性,解构赋值可以取到这个属性。
对setPrototypeOf的补充(原型链这个东西搞得不是很懂啊)

用于更改指定对象的原型(即 [[Prototype]] 链中的对象)。这个方法允许你在运行时动态地更改一个对象继承的原型对象。

语法:

Object.setPrototypeOf(object, prototype)

参数

  • object: 你要更改其原型的对象。
  • prototype: 你希望设置为 object 的新原型的对象,或者 null 如果你想让对象没有原型。

举例

let parent = {
  greet: function() {
    console.log('Hello!');
  }
};

let child = {};

// 设置 child 的原型为 parent
Object.setPrototypeOf(child, parent);

child.greet();  // 输出 "Hello!"

注意事项

  • 如果一个对象已经继承了另一个对象的原型,那么改变它的原型可能会导致意料之外的行为,尤其是在原型链中有共享状态的情况下。
  • 在严格模式下,如果提供的 prototype 参数不是有效的(例如,不是一个对象也不是 null),则会抛出 TypeError
  • 如果你尝试将一个对象的原型设置为其已经具有的原型,Object.setPrototypeOf 不会抛出错误,但也不会做任何事情。
6.对象的属性值严格等于undefined
var {x = 3} = {x: undefined};
x // 3

var {x = 3} = {x: null};
x // null
//上面代码中,属性`x`等于`null`,因为`null`与`undefined`不严格相等,所以是个有效的赋值,导致默认值`3`不会生效。
7.注意点

如果要将一个已经声明的变量用于解构赋值,必须非常小心。

// 错误的写法
let x;
{x} = {x: 1};
// SyntaxError: syntax error
//上面代码的写法会报错,因为 JavaScript 引擎会将`{x}`理解成一个代码块,从而发生语法错误。只有不将大括号写在行首,避免 JavaScript 将其解释为代码块,才能解决这个问题。

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

解构赋值允许等号左边的模式之中,不放置任何变量名。因此,可以写出非常古怪的赋值表达式。

({} = [true, false]);
({} = 'abc');
({} = []);
//语法是合法的,可以执行,虽然毫无意义

由于数组本质是特殊的对象,因此可以对数组进行对象属性的解构。

let arr = [1, 2, 3];
let {0 : first, [arr.length - 1] : last} = arr;
first // 1
last // 3
//这个代码相当于是let {a[0]:first,a[2]:last} = arr

三,字符串的解构赋值

1.字符串也可以解构赋值。这是因为此时,字符串被转换成了一个类似数组的对象。

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

2.类似数组的对象都有一个length属性,因此还可以对这个属性解构赋值。

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

四,数值和布尔值的解构赋值

解构赋值时,如果等号右边是数值和布尔值,则会先转为对象。

let {toString: s} = 123;
s === Number.prototype.toString // true

let {toString: s} = true;
s === Boolean.prototype.toString // true
//上面代码中,数值和布尔值的包装对象都有`toString`属性,因此变量`s`都能取到值。
//就是相当于以下代码
let numberObj = new Number(123);
let s = numberObj.toString;
//由于 `Number` 对象的原型链指向 `Number.prototype`,而 `Number.prototype` 上有 `toString` 方法

let booleanObj = new Boolean(true);
let s = booleanObj.toString;
//由于 `Boolean` 对象的原型链指向 `Boolean.prototype`,而 `Boolean.prototype` 上有 `toString` 方法

解构赋值的规则是,只要等号右边的值不是对象或数组,就先将其转为对象。由于undefinednull无法转为对象,所以对它们进行解构赋值,都会报错。

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

五,函数参数的解构赋值

1.函数的参数也可以使用解构赋值。

function add([x, y]){
  return x + y;
}

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

//步骤
//1.数组有两个子函数[1,2],[3,4],假设arr=[[1, 2], [3, 4]]

//2.相当于是arr.map(([a, b]) => a + b);

//3.再相当于是arr.map(function([a,b]) {
//    return a+b
//})

//4.就是相当于对arr中的两个子对象中的元素分别求和
//第一次对[1,2]执行函数,结果为3
//第二次对[3,4]执行函数,结果为7

对map函数的补充

用于创建一个新的数组,其结果是该数组中的每个元素都调用了一个提供的函数进行处理。map 方法不会修改原数组,而是返回一个新的数组。

基本语法

array.map(callback[, thisArg])

参数

  • callback:一个函数,对于数组中的每个元素都会调用一次。回调函数接受三个参数:

    • element:当前正在处理的元素。
    • index:当前元素的索引。
    • array:调用 map 方法的数组。
  • thisArg:可选参数,指定了回调函数执行时的 this 值。

返回值

map 方法返回一个新的数组,其中包含原始数组中的每个元素经过回调函数处理后的结果。

注意事项

  • map 方法不会改变原始数组。
  • 如果回调函数返回 undefined 或者不返回任何值,则新的数组中的对应位置将是 undefined
  • 如果原始数组中的某个元素是 undefined 或者 null,则回调函数不会被调用,并且新数组中的对应位置也是 undefined

举例

let numbers = [1, 2, 3, 4, 5];
let doubledNumbers = numbers.map(function(element) {
  return element * 2;
});

console.log(doubledNumbers); // 输出 [2, 4, 6, 8, 10]
let numbers = [1, 2, 3, 4, 5];
let doubledNumbers = numbers.map(element => element * 2);

console.log(doubledNumbers); // 输出 [2, 4, 6, 8, 10]
let numbers = [1, 2, 3, 4, 5];
let indexedNumbers = numbers.map((element, index) => {
  return `${element} at index ${index}`;
});

console.log(indexedNumbers); // 输出 ['1 at index 0', '2 at index 1', '3 at index 2', '4 at index 3', '5 at index 4']
let obj = {
  values: [1, 2, 3, 4, 5],
  multiplyByTwo: function(element, index, array) {
    return element * 2;
  }
};

let doubledValues = obj.values.map(obj.multiplyByTwo, obj);
console.log(doubledValues); // 输出 [2, 4, 6, 8, 10]

//步骤
//1.obj.values.map(obj.multiplyByTwo, obj) 等价于
//[1, 2, 3, 4, 5].map(obj.multiplyByTwo, obj)

//2.[1, 2, 3, 4, 5].map(obj.multiplyByTwo, obj) 等价与
//[1, 2, 3, 4, 5].map(function(element, index, array) {
    return element * 2;
  }, obj)
  
 //3. 第二个参数 `obj` 是 `thisArg`,用于指定 `multiplyByTwo` 函数执行时的 `this` 值。
 //通过传递 `thisArg`,我们可以确保回调函数中的 `this` 值是对象 `obj`,从而可以在回调函数中访问对象的其他属性或方法。
 
// 4.举例
let obj = {
  values: [1, 2, 3, 4, 5],
  multiplyByTwo: function(element, index, array) {
    // 在这里可以访问对象的其他属性或方法
    this.logMessage(`Processing element: ${element}`);
    return element * 2;
  },
  logMessage: function(message) {
    console.log(message);
  }
};

let doubledValues = obj.values.map(obj.multiplyByTwo, obj);

console.log(doubledValues); 
// 输出结果
//Processing element: 1
//Processing element: 2
//Processing element: 3
//Processing element: 4
//Processing element: 5
//[ 2, 4, 6, 8, 10 ]

2.函数参数的解构也可以使用默认值。

function move({x = 0, y = 0} = {}) {
  return [x, y];
}

move({x: 3, y: 8}); // [3, 8]
move({x: 3}); // [3, 0]
move({}); // [0, 0]
move(); // [0, 0]

//跟以下写法完全不一样
function move({x, y} = { x: 0, y: 0 }) {
  return [x, y];
}

move({x: 3, y: 8}); // [3, 8]
move({x: 3}); // [3, undefined]
move({}); // [undefined, undefined]
move(); // [0, 0]

//步骤(图在下面)
1.move({x:3,y:8})就是将{x:3,y:8}传递给move中的参数(move(x,y)),就是相当于{x:3,y:8} = {x,y},所以[x,y] = [3,8]
2.move({x:3})就是将{x:3}传递给move中的参数(move(x,y)),就是相当于{x:3} = {x,y},所以[x,y] = [3,undefined]
3.move({})就是将{}传递给move中的参数(move(x,y)),就是相当于{} = {x,y},所以[x,y] = [undefined,undefined]
4.move()就是将不把参数传递给move中的参数(move(x,y)),就是相当于其中的参数取默认值[0,0]

image.png

六,三种解构赋值不得使用圆括号的情况

1.变量声明语句

// 全部报错
let [(a)] = [1];

let {x: (c)} = {};
let ({x: c}) = {};
let {(x: c)} = {};
let {(x): c} = {};

let { o: ({ p: p }) } = { o: { p: 2 } };

2.函数参数也属于变量声明,因此不能带有圆括号。

// 报错
function f([(z)]) { return z; }
// 报错
function f([z,(x)]) { return x; }

3.赋值语句的模式

// 全部报错
({ p: a }) = { p: 42 };
([a]) = [5];
// 报错
[({ p: a }), { x: c }] = [{}, {}];

七,可以使用圆括号的情况

可以使用圆括号的情况只有一种:赋值语句的非模式部分,可以使用圆括号。

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

上面三行语句都可以正确执行,因为首先它们都是赋值语句,而不是声明语句;其次它们的圆括号都不属于模式的一部分。第一行语句中,模式是取数组的第一个成员,跟圆括号无关;第二行语句中,模式是p,而不是d;第三行语句与第一行语句的性质一致。

八,用途

1.交换变量的值

let x = 1;
let y = 2;

[x, y] = [y, x];

2.从函数返回多个值(函数只能返回一个值,如果要返回多个值,只能将它们放在数组或对象里返回。有了解构赋值,取出这些值就非常方便。)

// 返回一个数组

function example() {
  return [1, 2, 3];
}
let [a, b, c] = example();
console.log(a,b,c)  //1 2 3
// 返回一个对象

function example() {
  return {
    foo: 1,
    bar: 2
  };
}
let { foo, bar } = example();
console.log(foo,bar)  //1 2
步骤:就是将example返回的[1,2,3]赋值给[a,b,c],就是相当于[a,b,c] = [1,2,3]

3.函数参数的定义(解构赋值可以方便地将一组参数与变量名对应起来。)

// 参数是一组有次序的值
function f([x, y, z]) {
    return [x,y,z]
}
console.log(f([1, 2, 3]));//[1,2,3]

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

4.提取 JSON 数据(解构赋值对提取 JSON 对象中的数据,尤其有用。)

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

let { id, status, data: number } = jsonData;

console.log(id, status, number);
// 42, "OK", [867, 5309]

5.函数参数的默认值(我感觉这个可忽略不计)

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

6.遍历 Map 结构

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

// 获取键名
for (let [key] of map) {
  // ...
}

// 获取键值
for (let [,value] of map) {
  // ...
}
对Map的补充:

const myMap = new Map() 是创建一个新的 Map 对象的 JavaScript 语句。Map 是一种内置的 JavaScript 对象,用于存储键值对数据。与传统的对象不同,Map 的键可以是任何类型的值(不仅仅是字符串),并且它提供了更多的方法来处理键值对。

Map 对象的特点

  1. 键的多样性Map 的键可以是任何类型的值,包括对象和其他原始类型。
  2. 有序性Map 保留键值对的插入顺序。
  3. 大小Map 明确支持 .size 属性,可以方便地获取键值对的数量。
  4. 迭代Map 可以使用 for...of 循环等迭代机制来遍历其内容。

new Map() 构造函数可以接受一个可选的数组作为参数,该数组中的每个元素都是一个包含两个元素的数组,第一个元素是键,第二个元素是值。如果不提供参数,则创建一个空的 Map

//不带参数
const myMap = new Map();

//携带参数
const myMap = new Map([
  ['one', 1],
  ['two', 2],
  ['three', 3]
]);

image.png

Map常用方法

1.set(key, value)(用于向 Map 中添加或更新一个键值对。)

myMap.set('four', 4);

2. get(key)(获取 Map 中对应键的值。)

const value = myMap.get('one'); // 返回 1

3. delete(key)(删除 Map 中对应键的键值对)

myMap.delete('one');

4.has(key)(检查 Map 是否包含特定的键。)

const hasOne = myMap.has('one'); // 返回 true

5.clear()(清空 Map 中的所有键值对。)

myMap.clear();

6.size(获取 Map 中键值对的数量。)

const size = myMap.size; // 返回 3

7.entries()keys()values()(获取 Map 的迭代器,可以用于遍历键值对、键或值。)

for (const [key, value] of myMap.entries()) {
  console.log(`Key: ${key}, Value: ${value}`);
}
//Key: one, Value: 1
//Key: two, Value: 2
//Key: three, Value: 3

for (const key of myMap.keys()) {
  console.log(`Key: ${key}`);
}
//Key: one
//Key: two
//Key: three

for (const value of myMap.values()) {
  console.log(`Value: ${value}`);
}
//Value: 1
//Value: 2
//Value: 3

8.forEach(callback)(遍历 Map 中的所有键值对,并对每个键值对执行一个回调函数。)

myMap.forEach((value, key) => {
  console.log(`Key: ${key}, Value: ${value}`);
});
//Key: one, Value: 1
//Key: two, Value: 2
//Key: three, Value: 3
7.输入模块的指定方法

加载模块时,往往需要指定输入哪些方法。解构赋值使得输入语句非常清晰。

const { SourceMapConsumer, SourceNode } = require("source-map");
//是在 Node.js 环境中导入 `source-map` 库中的两个类:`SourceMapConsumer``SourceNode`

对require的补充

require 是 Node.js 中的一个核心模块函数,用于加载模块(包括内置模块,第三方模块,本地模块)。它允许你在 Node.js 环境中导入其他模块或库,并在当前模块中使用它们的功能。require 函数返回模块导出的对象。

//加载内置模块
const fs = require('fs');
const path = require('path');

//加载第三方模块
const express = require('express');
const bodyParser = require('body-parser');

//加载本地模块
const myModule = require('./myModule');

Node.js 为了提高性能,会对模块进行缓存。这意味着一旦一个模块被加载,它就会被缓存起来,下次再 require 同一个模块时,会直接从缓存中获取,而不会再次执行模块内的代码。

模块的导出和导入

模块可以通过 module.exportsexports 导出对象、函数或值,然后通过 require 导入。

在myModule.js文件中

// myModule.js
module.exports = {
  greet: function(name) {
    return 'Hello, ' + name + '!';
  },
  add: function(a, b) {
    return a + b;
  }
};

在app.js文件中

const myModule = require('./myModule');

console.log(myModule.greet('Alice')); // 输出 "Hello, Alice!"
console.log(myModule.add(1, 2)); // 输出 3

对导出模块的补充:(这个等后面再仔细写,现在已经没有任何精力了)

  • module.exports 是模块的默认导出对象。
  • exports 是 module.exports 的别名,通常用于导出多个函数或对象。
  • 在使用 module.exports 直接赋值时,会覆盖之前的导出内容;而使用 exports 时,实际上是修改 module.exports 的属性。