一,数组的解构赋值
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的数组。再举例[2][1]这个是对单数组[2],查找第二个元素,这个为undefined
-
访问数组的第一个元素:
[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` 方法
解构赋值的规则是,只要等号右边的值不是对象或数组,就先将其转为对象。由于undefined和null无法转为对象,所以对它们进行解构赋值,都会报错。
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]
六,三种解构赋值不得使用圆括号的情况
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 对象的特点
- 键的多样性:
Map的键可以是任何类型的值,包括对象和其他原始类型。 - 有序性:
Map保留键值对的插入顺序。 - 大小:
Map明确支持.size属性,可以方便地获取键值对的数量。 - 迭代:
Map可以使用for...of循环等迭代机制来遍历其内容。
new Map() 构造函数可以接受一个可选的数组作为参数,该数组中的每个元素都是一个包含两个元素的数组,第一个元素是键,第二个元素是值。如果不提供参数,则创建一个空的 Map。
//不带参数
const myMap = new Map();
//携带参数
const myMap = new Map([
['one', 1],
['two', 2],
['three', 3]
]);
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.exports 或 exports 导出对象、函数或值,然后通过 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的属性。