JavaScript 中使用最多的两种数据结构是 Object
和 Array
。
- 对象允许我们创建一个通过键存储数据项的单一实体。
- 数组允许我们将数据项收集到一个有序列表中。
但是,当我们将它们传递给函数时,我们可能并不需要全部。该函数可能只需要某些元素或属性。
解构赋值是一种特殊的语法,允许我们将数组或对象“解包”到一堆变量中,因为有时这样做更方便。
解构也适用于具有大量参数、默认值等的复杂函数。我们很快就会看到。
数组解构
以下是如何将数组解构成变量的示例
// we have an array with a name and surname
let arr = ["John", "Smith"]
// destructuring assignment
// sets firstName = arr[0]
// and surname = arr[1]
let [firstName, surname] = arr;
alert(firstName); // John
alert(surname); // Smith
现在我们可以使用变量而不是数组成员。
当与 split
或其他返回数组的方法结合使用时,它看起来很棒
let [firstName, surname] = "John Smith".split(' ');
alert(firstName); // John
alert(surname); // Smith
正如你所见,语法很简单。不过有几个特殊细节。让我们看更多示例以更好地理解它。
它被称为“解构赋值”,因为它通过将项复制到变量中来“解构”。但是,数组本身不会被修改。
这只是写的一种更短的方式
// let [firstName, surname] = arr;
let firstName = arr[0];
let surname = arr[1];
还可以通过额外的逗号丢弃数组中不需要的元素
// second element is not needed
let [firstName, , title] = ["Julius", "Caesar", "Consul", "of the Roman Republic"];
alert( title ); // Consul
在上面的代码中,数组的第二个元素被跳过,第三个元素被分配给title
,数组的其余项也被跳过(因为没有变量可以分配它们)。
…实际上,我们可以将其与任何可迭代对象一起使用,而不仅仅是数组
let [a, b, c] = "abc"; // ["a", "b", "c"]
let [one, two, three] = new Set([1, 2, 3]);
这样做是因为在内部,解构赋值通过迭代右侧值来工作。这是一种语法糖,用于对=
右侧的值调用for..of
并分配值。
我们可以在左侧使用任何“可赋值”项。
例如,对象属性
let user = {};
[user.name, user.surname] = "John Smith".split(' ');
alert(user.name); // John
alert(user.surname); // Smith
在上一章中,我们看到了 Object.entries(obj) 方法。
我们可以将其与解构一起使用,以循环遍历对象的键和值
let user = {
name: "John",
age: 30
};
// loop over the keys-and-values
for (let [key, value] of Object.entries(user)) {
alert(`${key}:${value}`); // name:John, then age:30
}
对于Map
的类似代码更简单,因为它可迭代
let user = new Map();
user.set("name", "John");
user.set("age", "30");
// Map iterates as [key, value] pairs, very convenient for destructuring
for (let [key, value] of user) {
alert(`${key}:${value}`); // name:John, then age:30
}
有一个众所周知的技巧,可以使用解构赋值交换两个变量的值
let guest = "Jane";
let admin = "Pete";
// Let's swap the values: make guest=Pete, admin=Jane
[guest, admin] = [admin, guest];
alert(`${guest} ${admin}`); // Pete Jane (successfully swapped!)
这里我们创建了一个由两个变量组成的临时数组,并立即以交换的顺序对其进行解构。
我们可以用这种方式交换两个以上的变量。
其余的“…”
通常,如果数组比左侧的列表长,“额外”的项将被省略。
例如,这里只取两个项,其余的都将被忽略
let [name1, name2] = ["Julius", "Caesar", "Consul", "of the Roman Republic"];
alert(name1); // Julius
alert(name2); // Caesar
// Further items aren't assigned anywhere
如果我们还想收集所有后续项,我们可以添加另一个参数,该参数使用三个点"..."
获取“其余项”
let [name1, name2, ...rest] = ["Julius", "Caesar", "Consul", "of the Roman Republic"];
// rest is an array of items, starting from the 3rd one
alert(rest[0]); // Consul
alert(rest[1]); // of the Roman Republic
alert(rest.length); // 2
rest
的值是剩余数组元素的数组。
我们可以用任何其他变量名代替 rest
,只要确保它前面有三个点,并且在解构赋值中排在最后。
let [name1, name2, ...titles] = ["Julius", "Caesar", "Consul", "of the Roman Republic"];
// now titles = ["Consul", "of the Roman Republic"]
默认值
如果数组比左侧的变量列表短,则不会出现错误。不存在的值被认为是未定义的
let [firstName, surname] = [];
alert(firstName); // undefined
alert(surname); // undefined
如果我们希望“默认”值替换缺失的值,我们可以使用 =
提供它
// default values
let [name = "Guest", surname = "Anonymous"] = ["Julius"];
alert(name); // Julius (from array)
alert(surname); // Anonymous (default used)
默认值可以是更复杂的表达式,甚至可以是函数调用。仅当未提供值时才对其进行求值。
例如,这里我们对两个默认值使用 prompt
函数
// runs only prompt for surname
let [name = prompt('name?'), surname = prompt('surname?')] = ["Julius"];
alert(name); // Julius (from array)
alert(surname); // whatever prompt gets
请注意:prompt
仅对缺失值(surname
)运行。
对象解构
解构赋值也适用于对象。
基本语法是
let {var1, var2} = {var1:…, var2:…}
我们应该在右侧有一个现有的对象,我们希望将其拆分为变量。左侧包含一个类似对象的“模式”,用于对应属性。在最简单的情况下,这是一个 {...}
中的变量名列表。
例如
let options = {
title: "Menu",
width: 100,
height: 200
};
let {title, width, height} = options;
alert(title); // Menu
alert(width); // 100
alert(height); // 200
属性 options.title
、options.width
和 options.height
被分配给相应的变量。
顺序无关紧要。这也行
// changed the order in let {...}
let {height, width, title} = { title: "Menu", height: 200, width: 100 }
左侧的模式可能更复杂,并指定属性与变量之间的映射。
如果我们想将属性分配给具有另一个名称的变量,例如,使 options.width
进入名为 w
的变量,那么我们可以使用冒号设置变量名
let options = {
title: "Menu",
width: 100,
height: 200
};
// { sourceProperty: targetVariable }
let {width: w, height: h, title} = options;
// width -> w
// height -> h
// title -> title
alert(title); // Menu
alert(w); // 100
alert(h); // 200
冒号显示“什么:去哪里”。在上面的示例中,属性 width
转到 w
,属性 height
转到 h
,而 title
被分配给相同的名称。
对于可能缺少的属性,我们可以使用 "="
设置默认值,如下所示
let options = {
title: "Menu"
};
let {width = 100, height = 200, title} = options;
alert(title); // Menu
alert(width); // 100
alert(height); // 200
就像数组或函数参数一样,默认值可以是任何表达式,甚至可以是函数调用。如果未提供值,它们将被求值。
在下面的代码中,prompt
询问 width
,但不询问 title
let options = {
title: "Menu"
};
let {width = prompt("width?"), title = prompt("title?")} = options;
alert(title); // Menu
alert(width); // (whatever the result of prompt is)
我们还可以同时组合冒号和等号
let options = {
title: "Menu"
};
let {width: w = 100, height: h = 200, title} = options;
alert(title); // Menu
alert(w); // 100
alert(h); // 200
如果我们有一个具有许多属性的复杂对象,我们可以只提取我们需要的属性
let options = {
title: "Menu",
width: 100,
height: 200
};
// only extract title as a variable
let { title } = options;
alert(title); // Menu
剩余模式“…”
如果对象比我们拥有的变量具有更多属性怎么办?我们可以取一些,然后在某个地方分配“其余”吗?
我们可以使用剩余模式,就像我们对数组所做的那样。它不受一些较旧的浏览器(IE,使用 Babel 来填充它)支持,但在现代浏览器中可以使用。
它看起来像这样
let options = {
title: "Menu",
height: 200,
width: 100
};
// title = property named title
// rest = object with the rest of properties
let {title, ...rest} = options;
// now title="Menu", rest={height: 200, width: 100}
alert(rest.height); // 200
alert(rest.width); // 100
let
,那就完了在上面的示例中,变量直接在赋值中声明:let {…} = {…}
。当然,我们也可以使用现有的变量,而无需 let
。但有一个问题。
这不起作用
let title, width, height;
// error in this line
{title, width, height} = {title: "Menu", width: 200, height: 100};
问题在于 JavaScript 将主代码流(不在另一个表达式内)中的 {...}
视为代码块。此类代码块可用于对语句进行分组,如下所示
{
// a code block
let message = "Hello";
// ...
alert( message );
}
因此,JavaScript 假设我们有一个代码块,这就是出现错误的原因。我们希望进行解构。
为了向 JavaScript 表明这不是一个代码块,我们可以用括号 (...)
将表达式括起来
let title, width, height;
// okay now
({title, width, height} = {title: "Menu", width: 200, height: 100});
alert( title ); // Menu
嵌套解构
如果对象或数组包含其他嵌套对象和数组,我们可以使用更复杂的左侧模式来提取更深层次的部分。
在下面的代码中,options
在 size
属性中具有另一个对象,在 items
属性中具有一个数组。赋值左侧的模式具有相同的结构,以便从它们中提取值
let options = {
size: {
width: 100,
height: 200
},
items: ["Cake", "Donut"],
extra: true
};
// destructuring assignment split in multiple lines for clarity
let {
size: { // put size here
width,
height
},
items: [item1, item2], // assign items here
title = "Menu" // not present in the object (default value is used)
} = options;
alert(title); // Menu
alert(width); // 100
alert(height); // 200
alert(item1); // Cake
alert(item2); // Donut
options
对象的所有属性(除了左侧不存在的 extra
)都分配给相应的变量
最后,我们从默认值获得了 width
、height
、item1
、item2
和 title
。
请注意,size
和 items
没有变量,因为我们取而代之的是它们的内容。
智能函数参数
有时,函数有很多参数,其中大多数是可选的。对于用户界面来说尤其如此。想象一个创建菜单的函数。它可能具有宽度、高度、标题、项目列表等。
下面是编写此类函数的一种糟糕方法
function showMenu(title = "Untitled", width = 200, height = 100, items = []) {
// ...
}
在现实生活中,问题是如何记住参数的顺序。通常,IDE 会尝试帮助我们,特别是如果代码有很好的文档记录,但仍然如此……另一个问题是如何在大多数参数默认情况下都正常时调用函数。
像这样吗?
// undefined where default values are fine
showMenu("My Menu", undefined, undefined, ["Item1", "Item2"])
这很丑陋。当我们处理更多参数时,它变得不可读。
解构来拯救我们!
我们可以将参数作为对象传递,函数会立即将它们解构为变量
// we pass object to function
let options = {
title: "My menu",
items: ["Item1", "Item2"]
};
// ...and it immediately expands it to variables
function showMenu({title = "Untitled", width = 200, height = 100, items = []}) {
// title, items – taken from options,
// width, height – defaults used
alert( `${title} ${width} ${height}` ); // My Menu 200 100
alert( items ); // Item1, Item2
}
showMenu(options);
我们还可以使用更复杂的解构,包括嵌套对象和冒号映射
let options = {
title: "My menu",
items: ["Item1", "Item2"]
};
function showMenu({
title = "Untitled",
width: w = 100, // width goes to w
height: h = 200, // height goes to h
items: [item1, item2] // items first element goes to item1, second to item2
}) {
alert( `${title} ${w} ${h}` ); // My Menu 100 200
alert( item1 ); // Item1
alert( item2 ); // Item2
}
showMenu(options);
完整语法与解构赋值相同
function({
incomingProperty: varName = defaultValue
...
})
然后,对于参数对象,将有一个变量 varName
用于属性 incomingProperty
,默认情况下使用 defaultValue
。
请注意,此类解构假设 showMenu()
确实有一个参数。如果我们希望所有值都为默认值,则应指定一个空对象
showMenu({}); // ok, all values are default
showMenu(); // this would give an error
我们可以通过将 {}
设置为整个参数对象的默认值来解决此问题
function showMenu({ title = "Menu", width = 100, height = 200 } = {}) {
alert( `${title} ${width} ${height}` );
}
showMenu(); // Menu 100 200
在上面的代码中,整个参数对象默认情况下为 {}
,因此总有一些东西可以解构。
总结
-
解构赋值允许立即将对象或数组映射到许多变量。
-
完整对象语法
let {prop : varName = defaultValue, ...rest} = object
这意味着属性
prop
应进入变量varName
,如果不存在此类属性,则应使用default
值。没有映射的对象属性将复制到
rest
对象。 -
完整数组语法
let [item1 = defaultValue, item2, ...rest] = array
第一个项目转到
item1
;第二个转到item2
,所有其余项目构成数组rest
。 -
可以从嵌套数组/对象中提取数据,为此,左侧必须与右侧具有相同的结构。
评论
<code>
标记,对于多行,请将其包装在<pre>
标记中,对于超过 10 行,请使用沙箱(plnkr、jsbin、codepen…)