了解JavaScript中的解构、Rest参数和传播语法

来自菜鸟教程
跳转至:导航、​搜索

作者选择了 COVID-19 Relief Fund 作为 Write for DOnations 计划的一部分来接受捐赠。

介绍

自 ECMAScript 规范的 2015 版 以来,JavaScript 语言已经可以使用许多用于处理 arraysobjects 的新功能。 您将在本文中学习的一些值得注意的语法是 destructuringrest parametersspread 语法。 这些特性提供了更直接的方法来访问数组或对象的成员,并且可以使使用这些数据结构更快、更简洁。

许多其他语言没有相应的解构、剩余参数和展开语法,因此这些特性可能对新的 JavaScript 开发人员和来自其他语言的开发人员都有一个学习曲线。 在本文中,您将学习如何解构对象和数组,如何使用扩展运算符解包对象和数组,以及如何在函数调用中使用剩余参数。

解构

解构赋值 是一种语法,允许您将对象属性或数组项分配为变量。 这可以大大减少操作这些结构中的数据所需的代码行。 解构有两种类型:对象解构和数组解构。

对象解构

对象解构允许您使用对象属性作为值来创建新的 变量

考虑这个例子,一个表示带有 idtitledate 音符的对象:

const note = {
  id: 1,
  title: 'My first note',
  date: '01/01/1970',
}

传统上,如果您想为每个属性创建一个新变量,则必须单独分配每个变量,并进行大量重复:

// Create variables from the Object properties
const id = note.id
const title = note.title
const date = note.date

使用对象解构,这一切都可以在一行中完成。 通过将每个变量括在大括号 {} 中,JavaScript 将从每个具有相同名称的属性创建新变量:

// Destructure properties into variables
const { id, title, date } = note

现在,console.log() 新变量:

console.log(id)
console.log(title)
console.log(date)

您将获得原始属性值作为输出:

Output1
My first note
01/01/1970

注意: 解构对象不会修改原始对象。 您仍然可以调用原始的 note 并且其所有条目都完好无损。


对象解构的默认赋值会创建与对象属性同名的新变量。 如果您不希望新变量与属性名称同名,您还可以选择使用冒号 (:) 重命名新变量来决定新名称,如 [ X198X] 如下:

// Assign a custom name to a destructured value
const { id: noteId, title, date } = note

将新变量 noteId 记录到控制台:

console.log(noteId)

您将收到以下输出:

Output1

您还可以解构嵌套对象值。 例如,将 note 对象更新为具有嵌套的 author 对象:

const note = {
  id: 1,
  title: 'My first note',
  date: '01/01/1970',
  author: {
    firstName: 'Sherlock',
    lastName: 'Holmes',
  },
}

现在您可以解构 note,然后再次解构以从 author 属性创建变量:

// Destructure nested properties
const {
  id,
  title,
  date,
  author: { firstName, lastName },
} = note

接下来,使用 模板文字 记录新变量 firstNamelastName

console.log(`${firstName} ${lastName}`)

这将给出以下输出:

OutputSherlock Holmes

请注意,在此示例中,尽管您可以访问 author 对象的内容,但无法访问 author 对象本身。 为了访问对象及其嵌套值,您必须单独声明它们:

// Access object and nested values
const {
  author,
  author: { firstName, lastName },
} = note

console.log(author)

此代码将输出 author 对象:

Output{firstName: "Sherlock", lastName: "Holmes"}

解构对象不仅有助于减少您必须编写的代码量; 它还允许您定位对您关心的属性的访问。

最后,解构可用于访问原始值的对象属性。 例如,String 是字符串的全局对象,并具有 length 属性:

const { length } = 'A string'

这将找到字符串的固有长度属性并将其设置为等于 length 变量。 记录 length 以查看这是否有效:

console.log(length)

您将获得以下输出:

Output8

字符串 A string 在此处被隐式转换为对象以检索 length 属性。

数组解构

数组解构允许您使用数组项作为值来创建新变量。 考虑这个例子,一个包含日期各个部分的数组:

const date = ['1970', '12', '01']

JavaScript 中的数组保证保持它们的顺序,所以在这种情况下,第一个索引总是一年,第二个索引是月份,依此类推。 知道了这一点,您可以从数组中的项目创建变量:

// Create variables from the Array items
const year = date[0]
const month = date[1]
const day = date[2]

但是手动执行此操作会占用代码中的大量空间。 使用数组解构,您可以按顺序从数组中解压缩值并将它们分配给它们自己的变量,如下所示:

// Destructure Array values into variables
const [year, month, day] = date

现在记录新变量:

console.log(year)
console.log(month)
console.log(day)

您将获得以下输出:

Output1970
12
01

可以通过在逗号之间将解构语法留空来跳过值:

// Skip the second item in the array
const [year, , day] = date

console.log(year)
console.log(day)

运行它会给出 yearday 的值:

Output1970
01

嵌套数组也可以被解构。 首先,创建一个嵌套数组:

// Create a nested array
const nestedArray = [1, 2, [3, 4], 5]

然后解构该数组并记录新变量:

// Destructure nested items
const [one, two, [three, four], five] = nestedArray

console.log(one, two, three, four, five)

您将收到以下输出:

Output1 2 3 4 5

解构语法可用于解构函数中的参数。 为了测试这一点,您将从 Object.entries() 中解构 keysvalues

首先,声明 note 对象:

const note = {
  id: 1,
  title: 'My first note',
  date: '01/01/1970',
}

给定这个对象,您可以通过解构参数来列出键值对,因为它们被传递给 forEach() 方法

// Using forEach
Object.entries(note).forEach(([key, value]) => {
  console.log(`${key}: ${value}`)
})

或者你可以使用 for 循环 来完成同样的事情:

// Using a for loop
for (let [key, value] of Object.entries(note)) {
  console.log(`${key}: ${value}`)
}

无论哪种方式,您都会收到以下信息:

Outputid: 1
title: My first note
date: 01/01/1970

对象解构和数组解构可以组合在一个解构赋值中。 默认参数也可以与解构一起使用,如本例所示,将默认日期设置为new Date()

首先,声明 note 对象:

const note = {
  title: 'My first note',
  author: {
    firstName: 'Sherlock',
    lastName: 'Holmes',
  },
  tags: ['personal', 'writing', 'investigations'],
}

然后解构对象,同时设置一个新的 date 变量,默认值为 new Date()

const {
  title,
  date = new Date(),
  author: { firstName },
  tags: [personalTag, writingTag],
} = note

console.log(date)

console.log(date) 然后将给出类似于以下的输出:

OutputFri May 08 2020 23:53:49 GMT-0500 (Central Daylight Time)

如本节所示,解构赋值语法为 JavaScript 增加了很多灵活性,并允许您编写更简洁的代码。 在下一节中,您将看到如何使用扩展语法将数据结构扩展为它们的组成数据条目。

传播

Spread 语法 (...) 是 JavaScript 的另一个有用的补充,用于处理数组、对象和函数调用。 Spread 允许对对象和可迭代对象(如数组)进行解包或扩展,可用于制作数据结构的浅拷贝,以增加数据操作的便利性。

用数组传播

Spread 可以使用数组简化常见任务。 例如,假设您有两个数组并想要组合它们:

// Create an Array
const tools = ['hammer', 'screwdriver']
const otherTools = ['wrench', 'saw']

最初您将使用 concat() 连接两个数组:

// Concatenate tools and otherTools together
const allTools = tools.concat(otherTools)

现在您还可以使用 spread 将数组解包到一个新数组中:

// Unpack the tools Array into the allTools Array
const allTools = [...tools, ...otherTools]

console.log(allTools)

运行它会给出以下结果:

Output["hammer", "screwdriver", "wrench", "saw"]

这对于不变性特别有用。 例如,您可能正在使用将 users 存储在对象数组中的应用程序:

// Array of users
const users = [
  { id: 1, name: 'Ben' },
  { id: 2, name: 'Leslie' },
]

您可以使用 push 修改现有数组并添加新用户,这将是可变选项:

// A new user to be added
const newUser = { id: 3, name: 'Ron' }

users.push(newUser)

但这会改变我们可能想要保留的 user 数组。

Spread 允许您从现有数组创建一个新数组并在末尾添加一个新项:

const updatedUsers = [...users, newUser]

console.log(users)
console.log(updatedUsers)

现在新数组 updatedUsers 有了新用户,但原来的 users 数组保持不变:

Output[{id: 1, name: "Ben"}
 {id: 2, name: "Leslie"}]

[{id: 1, name: "Ben"}
 {id: 2, name: "Leslie"}
 {id: 3, name: "Ron"}]

创建数据副本而不是更改现有数据有助于防止意外更改。 在 JavaScript 中,当您创建一个对象或数组并将其分配给另一个变量时,您实际上并不是在创建一个新对象,而是在传递一个引用。

以这个例子为例,其中创建了一个数组并将其分配给另一个变量:

// Create an Array
const originalArray = ['one', 'two', 'three']

// Assign Array to another variable
const secondArray = originalArray

删除第二个数组的最后一项将修改第一个:

// Remove the last item of the second Array
secondArray.pop()

console.log(originalArray)

这将给出输出:

Output["one", "two"]

Spread 允许您制作数组或对象的浅拷贝,这意味着任何顶级属性都将被克隆,但嵌套对象仍将通过引用传递。 对于简单的数组或对象,您可能只需要一个浅拷贝。

如果您编写相同的示例代码但复制带有 spread 的数组,则将不再修改原始数组:

// Create an Array
const originalArray = ['one', 'two', 'three']

// Use spread to make a shallow copy
const secondArray = [...originalArray]

// Remove the last item of the second Array
secondArray.pop()

console.log(originalArray)

以下将记录到控制台:

Output["one", "two", "three"]

Spread 也可用于将 set 或任何其他 iterable 转换为数组。

创建一个新集并向其中添加一些条目:

// Create a set
const set = new Set()

set.add('octopus')
set.add('starfish')
set.add('whale')

接下来,将扩展运算符与 set 一起使用并记录结果:

// Convert Set to Array
const seaCreatures = [...set]

console.log(seaCreatures)

这将给出以下内容:

Output["octopus", "starfish", "whale"]

这对于从字符串创建数组也很有用:

const string = 'hello'

const stringArray = [...string]

console.log(stringArray)

这将给出一个数组,其中每个字符作为数组中的一项:

Output["h", "e", "l", "l", "o"]

用对象传播

处理对象时,spread 可用于复制和更新对象。

最初, Object.assign() 用于复制对象:

// Create an Object and a copied Object with Object.assign()
const originalObject = { enabled: true, darkMode: false }
const secondObject = Object.assign({}, originalObject)

secondObject 现在将是 originalObject 的克隆。

使用扩展语法可以简化这一点——您可以通过将对象扩展为新对象来浅拷贝对象:

// Create an object and a copied object with spread
const originalObject = { enabled: true, darkMode: false }
const secondObject = { ...originalObject }

console.log(secondObject)

这将导致以下结果:

Output{enabled: true, darkMode: false}

就像数组一样,这只会创建一个浅拷贝,嵌套对象仍然会通过引用传递。

以不可变的方式在现有对象上添加或修改属性可以通过扩展简化。 在此示例中,isLoggedIn 属性被添加到 user 对象:

const user = {
  id: 3,
  name: 'Ron',
}

const updatedUser = { ...user, isLoggedIn: true }

console.log(updatedUser)

这将输出以下内容:

Output{id: 3, name: "Ron", isLoggedIn: true}

通过传播更新对象需要注意的一件重要事情是,任何嵌套对象也必须被传播。 例如,假设在 user 对象中有一个嵌套的 organization 对象:

const user = {
  id: 3,
  name: 'Ron',
  organization: {
    name: 'Parks & Recreation',
    city: 'Pawnee',
  },
}

如果您尝试向 organization 添加新项目,它将覆盖现有字段:

const updatedUser = { ...user, organization: { position: 'Director' } }

console.log(updatedUser)

这将导致以下结果:

Outputid: 3
name: "Ron"
organization: {position: "Director"}

如果可变性不是问题,则可以直接更新该字段:

user.organization.position = 'Director'

但是由于我们正在寻求一个不可变的解决方案,我们可以传播内部对象以保留现有属性:

const updatedUser = {
  ...user,
  organization: {
    ...user.organization,
    position: 'Director',
  },
}

console.log(updatedUser)

这将给出以下内容:

Outputid: 3
name: "Ron"
organization: {name: "Parks & Recreation", city: "Pawnee", position: "Director"}

使用函数调用传播

Spread 也可以与函数调用中的参数一起使用。

例如,下面是一个 multiply 函数,它接受三个参数并将它们相乘:

// Create a function to multiply three items
function multiply(a, b, c) {
  return a * b * c
}

通常,您会将三个值分别作为参数传递给函数调用,如下所示:

multiply(1, 2, 3)

这将给出以下内容:

Output6

但是,如果您要传递给函数的所有值都已存在于数组中,则扩展语法允许您将数组中的每个项目用作参数:

const numbers = [1, 2, 3]

multiply(...numbers)

这将给出相同的结果:

Output6

注:不展开,可以使用apply()来完成:

multiply.apply(null, [1, 2, 3])

这将给出:

Output6

现在您已经了解了 spread 如何缩短您的代码,您可以看看 ... 语法的另一种用法:rest 参数。

休息参数

您将在本文中学习的最后一个特性是 剩余参数 语法。 语法看起来与spread (...) 相同,但效果相反。 其余语法不会将数组或对象解包为单个值,而是创建一个包含不定数量参数的数组。

例如,在函数 restTest 中,如果我们希望 args 是一个由不定数量的参数组成的数组,我们可以有以下内容:

function restTest(...args) {
  console.log(args)
}

restTest(1, 2, 3, 4, 5, 6)

传递给 restTest 函数的所有参数现在都在 args 数组中可用:

Output[1, 2, 3, 4, 5, 6]

Rest 语法可以用作唯一的参数,也可以用作列表中的最后一个参数。 如果用作唯一参数,它将收集所有参数,但如果它位于列表的末尾,它将收集剩余的每个参数,如下例所示:

function restTest(one, two, ...args) {
  console.log(one)
  console.log(two)
  console.log(args)
}

restTest(1, 2, 3, 4, 5, 6)

这将单独获取前两个参数,然后将其余参数分组到一个数组中:

Output1
2
[3, 4, 5, 6]

在旧代码中,arguments 变量可用于收集传递给函数的所有参数:

function testArguments() {
  console.log(arguments)
}

testArguments('how', 'many', 'arguments')

这将给出以下输出:

[secondary_label Output]1
Arguments(3) ["how", "many", "arguments"]

然而,这有一些缺点。 首先,arguments 变量不能与箭头函数一起使用。

const testArguments = () => {
  console.log(arguments)
}

testArguments('how', 'many', 'arguments')

这会产生一个错误:

OutputUncaught ReferenceError: arguments is not defined

此外,arguments 不是真正的数组,如果不先转换为数组,则无法使用 map 和 filter 等方法。 它还将收集所有传递的参数,而不仅仅是其余的参数,如 restTest(one, two, ...args) 示例所示。

在解构数组时也可以使用 Rest:

const [firstTool, ...rest] = ['hammer', 'screwdriver', 'wrench']

console.log(firstTool)
console.log(rest)

这将给出:

Outputhammer
["screwdriver", "wrench"]

解构对象时也可以使用 Rest:

const { isLoggedIn, ...rest } = { id: 1, name: 'Ben', isLoggedIn: true }

console.log(isLoggedIn)
console.log(rest)

给出以下输出:

Outputtrue
{id: 1, name: "Ben"}

通过这种方式,rest 语法为收集不确定数量的项目提供了有效的方法。

结论

在本文中,您了解了解构、扩展语法和剩余参数。 总之:

  • 解构用于从数组项或对象属性创建变量。
  • 扩展语法用于解包可迭代对象,例如数组、对象和函数调用。
  • Rest 参数语法将从不定数量的值创建一个数组。

解构、剩余参数和扩展语法是 JavaScript 中的有用功能,有助于保持代码简洁明了。

如果您想了解解构的实际效果,请查看 How To Customize React Components with Props,它使用此语法来解构数据并将其传递给自定义前端组件。 如果您想了解有关 JavaScript 的更多信息,请返回我们的 如何在 JavaScript 中编码系列页面