合并多个JavaScript对象是一项常见的任务。不幸的是,JavaScript在提供方便的语法来进行合并方面草率。至少到现在为止。
在ES5中,您的解决方案_.extend(target, [sources])
来自Lodash(或任何替代产品),并且ES2015引入Object.assign(target, [sources])
。
幸运的是,对象传播语法(第3阶段的ECMAScript提议)是如何操作对象的一步,提供了一种简短易懂的语法。
在上面的示例中,...cat
将属性复制cat
到新对象中dog
。.sound
属性接收最终值'woof'
。
本文介绍了对象传播和其余语法。包括对象散布如何实现诸如对象克隆,合并,属性覆盖等的配方。
接下来是对可枚举属性的简短概述,以及如何区分自己的属性与继承的属性。这些是了解对象散布和休息如何工作的必要基础。
1.可数和自己的属性
JavaScript中的对象是键和值之间的关联。
密钥类型通常是字符串或符号。该值可以是原始类型(字符串,布尔值,数字undefined
或null
),对象或函数。
以下示例使用对象文字(也称为对象初始化程序)创建对象:
person
对象描述一个人的名字和姓氏。
1.1可枚举的属性
属性具有描述值的几种属性,以及可写,可枚举和可配置的状态。有关更多详细信息,请参见JavaScript中的对象属性。
Enumerable属性是一个布尔值,指示当枚举对象的属性时该属性是否可访问。
您可以使用Object.keys()
(访问自己的和可枚举的属性),for..in
语句(访问所有可枚举的属性)等枚举对象属性。
在对象文字{ prop1: 'val1', prop2: 'val2' }
中明确声明的属性是可枚举的。让我们看看可枚举的属性person
对象包含什么:
.name
和.surname
是枚举的属性person
的对象。
有趣的部分到了。来自源可枚举属性的对象散布副本:
现在,让我们.age
在person
对象上创建一个不可枚举的属性。然后查看点差的行为:
.name
和.surname
可枚举的属性从源对象复制person
到clone
。但是不可枚举.age
被忽略。
1.2自己的财产
JavaScript包含原型继承。因此,对象属性可以是自己的或继承的。
在对象常量中显式声明的属性是own。但是,对象从其原型接收的属性是继承的。
让我们创建一个对象personB
并将其原型设置为person
:
personB
对象具有自己的属性.profession
,并从其原型继承.name
和.surname
属性person
。
对象从源自己的属性传播副本,忽略继承的属性:
对象...personB
从源对象传播的副本personB
仅具有.profession
自己的属性。继承的.name
和.surname
被忽略。
对象分发语法从源对象复制 自己的和可枚举的属性。与Object.keys()函数返回的相同。
2.对象传播属性
对象文字中的对象传播语法从源对象中提取自己的和可枚举的属性,并将其复制到目标对象中。
附带说明一下,在许多方面,对象传播语法与等效Object.assign()
。上面的代码也可以这样实现:
一个对象文字可以具有多个对象散布,可以与常规属性声明任意组合:
2.1对象传播规则:后一个属性获胜
当散布多个对象并且某些属性具有相同的键时,如何计算最终的最终值集?规则很简单:
后期传播属性将 覆盖具有相同密钥的 早期属性
让我们继续一些例子。以下对象文字实例化了一只猫:
让我们玩弗兰肯斯坦博士,把这只猫变成狗。注意.sound
财产的价值:
后面的值将'woof'
覆盖前面的值'meow'
(来自cat
源对象)。这与后一个属性用相同的密钥覆盖最早的属性的规则匹配。
相同的规则适用于对象初始化程序的常规属性:
常规属性sound: 'woof'
获胜,因为它是最新的属性。
现在,如果交换散布对象的相对位置,结果将有所不同:
猫仍然是猫。尽管第一个源对象为.sound
属性提供了value 'woof'
,但是它被扩展属性中的后一个'meow'
值覆盖cat
。
对象散布和规则属性的相对位置很重要。扩展语法的这种效果允许执行配方,如对象克隆,合并对象,填充默认值。
让我们详细介绍这些食谱。
2.2克隆对象
使用传播语法锥化对象简短而富有表现力。以下示例创建bird
对象的副本:
...bird
在文字内部复制bird
到目标的自身和可枚举的属性birdClone
。结果birdClone
是的克隆bird
。
乍一看,克隆似乎很简单,但是有一些细微差别需要注意。
浅拷贝
对象传播对对象进行浅表复制。仅克隆对象本身,而不克隆嵌套实例。
laptop
有一个嵌套对象laptop.screen
。让我们克隆一下laptop
,看看它如何影响嵌套对象:
第一个比较laptop === laptopClone
是false
。主要对象已正确克隆。
但是laptop.screen === laptopClone.screen
评估为true
。这意味着laptop.screen
和laptopClone.screen
引用了未复制的同一嵌套对象。
好消息是您可以在任何级别传播属性。不费吹灰之力也可以克隆嵌套对象:
额外的扩展...laptop.screen
确保了嵌套对象也被克隆。很好,现在laptopDeepClone
是laptop
对象的完整克隆。
原型丢失
下面的代码片段声明一个类Game
,并创建该类的实例doom
:
现在,让我们克隆doom
从构造函数调用创建的实例。这可能会导致意外:
...doom
将自己的财产复制.name
到中doomClone
。仅此而已。
doomClone
是一个普通的JavaScript对象,原型为Object.prototype
,但并非Game.prototype
预期的那样。对象传播不会保留源对象的原型。
因此调用doomClone.getMessage()
会抛出一个TypeError
,因为doomClone
它不会继承getMessage()
方法。
要修复丢失的原型,请使用手动指示__proto__
:
__proto__
对象文字内部的内容确保doomFullClone
了必要的原型Game.prototype
。
不要在家尝试:__proto__
已弃用。我将其仅用于演示。
对象传播滞后于通过构造函数调用创建的实例,因为它不保留原型。目的是以浅薄的方式传播自己的和可枚举的属性,因此忽略原型的方法似乎是合理的。
附带说明一下,还有一种更合理的克隆doom
方法Object.assign()
:
好的,有了原型就足够了。我承诺。
2.3不可变对象更新
当同一对象在应用程序的许多地方共享时,直接对其进行修改可能会导致意外的副作用。跟踪此类修改是一项繁琐的任务。
更好的方法是使操作不变。不变性可以更好地控制对象的修改,并倾向于编写纯函数。即使在复杂的场景中,也因为数据流向单个方向,所以更容易确定对象更新的来源和原因。
对象散布便于以不变的方式修改对象。假设您有一个描述书籍版本的对象:
然后新的第六版问世。对象传播让您以不变的方式对此方案进行编程:
...book
字面量内的book
对象传播属性。手动枚举的属性edition: 6
并year: 2011
设置更新的属性值。
在末尾指定有效属性值,以匹配扩展规则,即后一个属性值使用相同的键覆盖前一个值。
newerBook
是具有更新属性的新对象。同时,原件book
保持原样。不变性得到满足。
2.4合并对象
合并很简单,因为您可以传播任意数量的对象的属性。
让我们合并3个对象以创建一个复合对象:
car
对象是通过合并三个对象创建的part1
,part2
和part3
。
不要忘记后一个财产胜出规则。它给出了合并具有相同键的多个对象的理由。
让我们更改一下前面的示例。现在part1
,part3
拥有一个新属性.configuration
:
第一个对象传播...part1
将的值设置.configuration
为'sedan'
。然而,后一个对象散布将...part3
覆盖先前的.configuration
值,最终使其变为'hatchback'
。
2.5使用默认值填充对象
一个对象在运行时可以具有不同的属性集。可能设置了某些属性,而其他属性可能会丢失。
在配置对象的情况下,可能会发生这种情况。用户只能指定配置的重要属性,但未指定的属性取自默认值。
让我们实现一个以给定宽度分成多行的multiline(str, config)
函数str
。
config
对象接受以下可选参数:
width
:要中断的字符数。默认为10
;newLine
:要添加到行末的字符串。默认为n
;indent
:要预定行的字符串。默认为空字符串''
。
multiline()
运作方式的几个例子:
config
参数接受不同的属性集:您可以指定1、2或3个属性,甚至根本不指定任何属性。
使用对象传播非常简单,即可用默认值填充配置对象。在对象文字内部,首先传播默认对象,然后传播配置对象:
让我们探索safeConfig
对象文字。
对象传播...defaultConfig
从默认值中提取属性。然后...config
使用自定义属性值覆盖以前的默认设置。
结果safeConfig
具有multiline()
主代码可以使用的全套属性。不管输入config
会丢失某些属性,您都相信它safeConfig
具有必要的值。
对象传播的默认设置实现非常直观。
2.6“我们需要更深入”
关于对象传播的最酷的事情是可以在嵌套对象上使用。在更新大对象时,这是一个很大的可读性胜利,建议在Object.assign()
替代方法上胜出。
以下box
对象定义了一个项目框:
box.size
描述盒子的大小并box.items
枚举盒子中包含的项目。
要通过增加来使框变高box.size.height
,只需将属性分布在嵌套对象上:
...box
确保biggerBox
从box
源接收属性。
更新嵌套对象的高度box.size
需要附加的对象文字{ ...box.size, height: 200 }
。此文字将的属性传播box.size
到新对象,并将高度更新为200
。
我喜欢通过一条语句执行多个更新的可能性。
将颜色更改为black
,将宽度增加为400
并添加新项目ruler
(使用spread array)怎么样?这很简单:
2.7传播未定义,空值和基元
当扩展的性质undefined
,null
或一个原始值没有性能被提取,并且没有错误被抛出。结果是一个普通的空对象:
对象散布提取无论从性能nothing
,missingObject
和two
。
当然,没有理由在原始值上使用对象散布。
3.对象其余属性
使用解构分配将对象的属性提取为变量后,可以将其余属性收集到其余对象中。
这是对象剩余属性很好地表现的:
销毁分配定义了一个新变量width
,并将其值设置为style.width
。其余对象...margin
解构赋值内收集剩余的属性marginLeft
和marginRight
到对象margin
。
对象剩余仅收集自己的和可枚举的属性。
请注意,对象其余部分必须是解构分配中的最后一个元素。因此,该代码const { ...margin , width } = style
无效,并会触发SyntaxError: Rest element must be last element
。
4。结论
对象传播要记住一些规则:
- 它从源对象中提取自己的和可枚举的属性
- 后期传播属性使用相同的密钥覆盖较早的属性
同时,对象传播简短而富有表现力,可以很好地在嵌套对象上使用,同时保持更新的不变性。它使使用默认属性轻松实现对象的克隆,合并和填充。
通过对象剩余语法实现解构分配后收集其余属性。
实际上,对象休息和传播属性是JavaScript的重要补充。
原著作者:德米特里·帕夫鲁汀
文章来源:国外
原著链接:
Dmitri Pavlutin Blogdmitripavlutin.comPS:原著文章内容为英文版本,建议使用360极速浏览器进行翻译阅读。