解构赋值(Destructuring Assignment)是 JavaScript ES6 引入的一项非常有用的特性,它允许我们快速地从数组或对象中提取值,并将它们赋给变量。这种方式使得代码更加简洁、易读,并且能够减少重复的访问和赋值操作。
1. 数组解构赋值
通过解构赋值,我们可以直接从数组中提取值并赋给变量。例如:
// 基本的数组解构赋值
const arr = [1, 2, 3];
const [a, b, c] = arr;console.log(a); // 1
console.log(b); // 2
console.log(c); // 3
说明:
a
、b
和c
分别从数组arr
中提取了值。- 解构赋值顺序是按数组的索引顺序进行的。
跳过元素:
如果我们只需要数组中的部分元素,可以跳过不需要的元素:
const arr = [1, 2, 3];
const [a, , c] = arr; // 跳过第二个元素console.log(a); // 1
console.log(c); // 3
默认值:
解构时,如果数组的某个位置没有值,可以为该变量提供默认值:
const arr = [1];
const [a, b = 2] = arr;console.log(a); // 1
console.log(b); // 2
2. 对象解构赋值
对象解构赋值允许我们从对象中提取特定的属性并赋给变量。
const person = { name: 'Alice', age: 25 };
const { name, age } = person;console.log(name); // Alice
console.log(age); // 25
说明:
- 变量名需要与对象属性的名称相同。如果变量名与对象属性名不同,可以通过
:
来重命名:
const person = { name: 'Alice', age: 25 };
const { name: personName, age: personAge } = person;console.log(personName); // Alice
console.log(personAge); // 25
默认值:
如果某个属性在对象中不存在,可以为该属性设置默认值:
const person = { name: 'Alice' };
const { name, age = 30 } = person;console.log(name); // Alice
console.log(age); // 30
嵌套解构:
你还可以对对象进行嵌套解构,从嵌套的对象中提取值:
const person = { name: 'Alice', address: { city: 'Wonderland', zip: '12345' } };
const { name, address: { city, zip } } = person;console.log(name); // Alice
console.log(city); // Wonderland
console.log(zip); // 12345
3. 解构赋值与剩余操作符
如果你需要从数组或对象中提取出剩余的元素或属性,可以使用剩余操作符 ...
。
数组的剩余解构:
const arr = [1, 2, 3, 4, 5];
const [first, second, ...rest] = arr;console.log(first); // 1
console.log(second); // 2
console.log(rest); // [3, 4, 5]
对象的剩余解构:
const person = { name: 'Alice', age: 25, city: 'Wonderland' };
const { name, ...otherInfo } = person;console.log(name); // Alice
console.log(otherInfo); // { age: 25, city: 'Wonderland' }
4. 函数参数中的解构赋值
解构赋值可以与函数参数结合使用,直接从传入的对象或数组中提取值:
数组解构作为函数参数:
function sum([a, b]) {return a + b;
}console.log(sum([3, 5])); // 8
对象解构作为函数参数:
function greet({ name, age }) {console.log(`Hello, ${name}! You are ${age} years old.`);
}greet({ name: 'Alice', age: 25 }); // Hello, Alice! You are 25 years old.
带默认值的函数参数解构:
function greet({ name = 'Guest', age = 18 } = {}) {console.log(`Hello, ${name}! You are ${age} years old.`);
}greet({ name: 'Alice' }); // Hello, Alice! You are 18 years old.
greet(); // Hello, Guest! You are 18 years old.
这段代码 [arr[i], arr[j]] = [arr[j], arr[i]];
是数组元素交换的语法,属于一种解构赋值的用法。它通常用于交换数组中两个元素的位置。
解释
-
解构赋值:这是 JavaScript ES6 中引入的一个特性,允许我们使用数组或对象的结构来进行赋值。
-
数组元素交换:在没有解构赋值的语法时,我们通常使用临时变量来交换两个元素,比如:
let temp = arr[i]; arr[i] = arr[j]; arr[j] = temp;
而使用解构赋值的方式可以减少临时变量的使用,使代码更简洁。通过这种方式,
arr[i]
和arr[j]
两个元素的值会交换。
使用场景
这个代码片段在Fisher-Yates 洗牌算法中非常常见,它用于随机打乱数组中的元素顺序。通过不断交换数组中的元素,从最后一个元素开始,逐步将每个元素与之前的元素交换,确保每个元素都有相等的概率出现在数组的任何位置。
示例:Fisher-Yates 洗牌算法实现
function shuffleArray(arr) {for (let i = arr.length - 1; i > 0; i--) {// 随机选择一个索引 j,其中 0 <= j <= iconst j = Math.floor(Math.random() * (i + 1));// 交换 arr[i] 和 arr[j][arr[i], arr[j]] = [arr[j], arr[i]];}
}const arr = [1, 2, 3, 4, 5];
shuffleArray(arr);
console.log(arr); // 打乱顺序后的数组
这段代码的关键点:
- Fisher-Yates 洗牌算法:这个算法的核心思想是从数组的最后一个元素开始,随机选择一个元素并与当前元素交换位置,直到整个数组都经过处理。这确保了数组中每个元素出现的顺序是随机的。
- 解构赋值:通过
[arr[i], arr[j]] = [arr[j], arr[i]];
来简洁地交换元素,无需使用额外的临时变量。
以下是将上述题目改成先输后赢情况的题目及对应答案,也就是目标变为让对方拿到最后一颗石子、最后一枚硬币等情况来取得胜利,供你参考:
💡题目 1:取石子游戏(先输后赢版)
- 有一堆石子,共 100 颗。甲、乙两人轮流从中取石子,每次最少取 1 颗,最多取 5 颗。取到最后一颗石子的人输。假设甲先取,问甲有没有必胜策略?如果有,甲第一步应该取几颗石子?
答案:
甲有必胜策略。因为要让乙取到最后一颗石子(即甲要避免取到最后一颗),那就需要保证每轮两人共取 6 颗石子(1 + 5 = 6,2 + 4 = 6,3 + 3 = 6 等情况),使得最后剩下 1 颗留给乙。100÷6 = 16……4,甲先取 4 颗石子,之后乙取 n 颗(1≤n≤5),甲就取 6 - n 颗,这样经过 16 轮后,就会剩下 1 颗石子留给乙去取,乙取到最后这颗石子,甲获胜。
💡题目 2:硬币游戏(先输后赢版)
- 桌上有 20 枚硬币,A、B 两人轮流拿硬币,每次可以拿 1 到 3 枚。谁拿到最后一枚硬币谁输。假设 A 先拿,A 有没有必胜策略?
答案:
A 有必胜策略。要保证 B 拿到最后一枚硬币,就要使得最后一轮留给 B 时只剩下 1 枚硬币。因为每次两人能取 1 到 3 枚,若想控制每轮取的数量,让两人一轮共取 4 枚硬币(1 + 3 = 4,2 + 2 = 4,3 + 1 = 4)是可行的。20÷4 = 5,没有余数,A 先拿 1 枚硬币,之后 B 拿 n 枚(1≤n≤3),A 就拿 4 - n 枚,这样经过 4 轮后,会剩下 1 枚硬币留给 B 去拿,A 获胜。
💡题目 3:划分数字(先输后赢版)
- 给定数字 10,A、B 两人轮流进行操作。操作可以是将当前数字减去 1 或者减去 2。谁先将数字减到 0 谁输。假设 A 先操作,A 有没有必胜策略?
答案:
A 有必胜策略。A 要避免自己把数字减到 0,那就需要让 B 去减到 0,所以 A 第一次要将 10 减去 2 变为 8。之后 B 操作,如果 B 减 1,A 就减 2;如果 B 减 2,A 就减 1,保证每轮两人共减去 3。因为 8÷3 = 2……2,经过 2 轮后,数字变为 2,此时不管 B 减 1 还是减 2,A 都能进行相应操作使得最后 B 把数字减到 0,A 获胜。
💡题目 4:报数游戏(先输后赢版)
- A、B 两人轮流报数,从 1 开始报,每次可以报 1 个或 2 个连续的数。谁报到 20 谁输。假设 A 先报,A 有没有必胜策略?
答案:
A 有必胜策略。A 要避免报到 20,要让 B 报到 20。A 先报 1 个数,报 1。然后 B 报数,如果 B 报 1 个数,A 就报 2 个数;如果 B 报 2 个数,A 就报 1 个数,保证每轮两人共报 3 个数。因为 (20 - 1)÷3 = 6……1,经过 6 轮后,正好轮到 B 报数,此时 B 只能报 20,B 输,A 获胜。
💡题目 5:火柴游戏(先输后赢版)
- 有 15 根火柴,A、B 两人轮流取火柴,每次可以取 1 根、2 根或 3 根。取到最后一根火柴的人输。假设 A 先取,A 有没有必胜策略?
答案:
A 有必胜策略。A 要让 B 取到最后一根火柴,也就是要保证最后剩下 1 根留给 B。因为每次两人取 1 到 3 根,控制每轮两人共取 4 根火柴(1 + 3 = 4,2 + 2 = 4,3 + 1 = 4)就行。15÷4 = 3……3,A 先取 3 根火柴,剩下 12 根,然后 B 取 n 根(1≤n≤3),A 就取 4 - n 根,这样经过 3 轮后,就会剩下 1 根火柴留给 B 去取,A 获胜。