In ES5 wurde _.extend(target, [sources])
in ES2015 mithilfe von Lodashs Object.assign(target, [sources])
(oder anderen Optionen) eingeführt.
Glücklicherweise bietet die spread
-Syntax für Objekte (ECMAScript Phase 3-Empfehlungen) eine kurze und leicht verständliche Syntax für die Manipulation von Objekten.
const cat = { legs: 4, sound: 'meow'};const dog = { ...cat, sound: 'woof'}; console.log(dog);
Im obigen Beispiel kopiert ...cat
das cat
-Objekt in ein neues dog
-Objekt. .sound
Der Wert des Attributs woof
wird am Ende platziert.
In diesem Artikel lernen wir die rest
- und spread
-Syntax von Objekten kennen. Sehen wir uns an, wie das Klonen, Zusammenführen, Umschreiben von Attributen usw. von Objekten implementiert wird.
Im Folgenden finden Sie eine kurze Zusammenfassung der aufzählbaren Eigenschaften und der Unterscheidung zwischen eigenen und geerbten Eigenschaften. Dies sind die notwendigen Grundlagen für das Verständnis der Syntax von Objekten spread
und rest
.
Aufzählungen und eigene Eigenschaften
In JavaScript ist ein Objekt eine Assoziation zwischen einem Schlüssel (key
) und einem Wert (value
). Der Typ von
key
ist normalerweise string
oder symbol
. Der Wert kann ein primitiver Typ (string
, boolean
, number
, undefined
oder null
) oder ein object
oder function
sein.
Die folgenden Objekte verwenden Objektliterale:
const person = { name: 'Dave', surname: 'Bowman'}
person
Das Objekt beschreibt name
und surname
einer Person.
Aufgezählte Eigenschaften
Eigenschaften (property
) verfügen über mehrere Eigenschaften (attribute
) zur Beschreibung von Werten sowie beschreibbaren, aufzählbaren und konfigurierbaren Zuständen. Weitere Einzelheiten hierzu finden Sie unter Objekteigenschaften in JavaScript.
Die aufzählbare Eigenschaft ist ein boolescher Wert, der sich darauf bezieht, ob auf die Eigenschaft im aufgezählten Objekt zugegriffen werden kann.
Mit Object.keys()
können Sie Objekteigenschaften aufzählen (für den Zugriff auf eigene und aufzählbare Eigenschaften). Sie können auch die for...in
-Anweisung verwenden (um alle aufzählbaren Eigenschaften aufzulisten) usw.
Durch Objektliterale {prop1: 'val1', prop2:'val2'}
deklarierte Eigenschaften sind aufzählbar. Schauen wir uns die aufzählbaren Eigenschaften an, die das person
-Objekt enthält:
const keys = Object.keys(person); console.log(keys);
name
und surname
sind durch die person
-Objekteigenschaft aufzählbar.
Hier kommt der lustige Teil. Objektverteilung wird aus der aufzählbaren Quelleigenschaft geklont:
console.log({ ...person })
Jetzt erstellen wir eine nicht aufzählbare Eigenschaft für das person
-Objekt age
. Schauen Sie sich dann das Object
-Verhalten von spread
an:
Object.defineProperty(person, 'age', { enumerable: false, // 属性不可枚举 value: 25}); console.log(person['age']); // => 25const clone = { ...person }; console.log(clone);
name
und surname
Aufzählungseigenschaften werden aus dem Quellobjekt person
< kopiert 🎜>-Objekt, aber das nicht aufzählbare Attribut clone
wird nicht hineinkopiert. age
-Objekt und setzen seine Eigenschaften auf personB
: Das person
const personB = Object.create(person, { profession: { value: 'Astronaut', enumerable: true } }); console.log(personB.hasOwnProperty('profession')); // => true console.log(personB.hasOwnProperty('name')); // => false console.log(personB.hasOwnProperty('surname')); // => false
-Objekt hat seine eigenen Eigenschaften personB
und von profession
Das person
und name
Attribute, die vom Attribut geerbt wurden. surname
Wenn Object Spread von seinen eigenen Quelleigenschaften kopiert, ignoriert es geerbte Eigenschaften. Das
const cloneB = { ...personB }; console.log(cloneB); // => { profession: 'Astronaut' }
-Objekt kopiert nur seine eigenen Eigenschaften ...personB
vom Quellobjekt personB
, ignoriert jedoch vererbbare profession
- und name
-Eigenschaften. surname
Object Spread kann seine eigenen und aufzählbaren Eigenschaften aus dem Quellobjekt kopieren. Dasselbe wieObjekteigenschaften verteilen Objektverteilung im Objektzeichen kann die eigenen und aufzählbaren Eigenschaften des Quellobjekts kopieren und auf das Zielobjekt kopieren..
Object.keys()
const targetObject = { ...sourceObject, property: 'Value'};
. Der obige Code kann auch wie folgt implementiert werden: Object.assign()
const targetObject = Object.assign( { }, sourceObject, { property: 'Value' } );
const targetObject = { ...sourceObject1, property1: 'Value 1', ...sourceObject2, ...sourceObject3, property2: 'Value 2'};
Object spread规则:最后属性获胜
当多个对象被传播,有一些属性具有相同的键时,那么是如何计算最终的值呢?规则很简单:后者扩展属性覆盖具有相同键的早期属性。
来看几个示例。下面的对象字面符实例化了一只猫:
const cat = { sound: 'meow', legs: 4}
让我们扮演Frankenstein博士,把这只猫变成一只狗。注意它的sound
属性值:
const dog = { ...cat, ...{ sound: 'woof' // <----- 覆盖 cat.sound } };console.log(dog); // => { sound: 'woof', legs: 4 }
后面的属性值woof
覆盖了前面的属性值meow
(来自cat
对象的sound
的值)。这符合使用相同的键值时,后一个属性值将覆盖最早的属性值的规则。
同样的规则也适用于对象初始化的规则属性:
const anotherDog = { ...cat, sound: 'woof' // <---- Overwrites cat.sound};console.log(anotherDog); // => { sound: 'woof', legs: 4 }
sound: 'woof'
规则最终获胜,那是因为他在最后。
现在如果你交换传播对象的相对位置,结果是不同的:
const stillCat = { ...{ sound: 'woof' // <---- Is overwritten by cat.sound }, ...cat};console.log(stillCat); // => { sound: 'meow', legs: 4 }
猫仍然是猫。尽管第一个源对象提供了sound
属性的值为woof
,但它还是被后面的cat
对象的sound
的属性值meow
覆盖了。
Object spread的位置和正则性质很重要。这种语法允许实现诸如对象克隆、合并和填充默认值之类的。
下面我们来看看。
对象克隆
使用Object Spread语法可以用一个简短而富有表现力的方式来克隆一个对象。下面的例子克隆了bird
对象:
const bird = { type: 'pigeon', color: 'white'};const birdClone = { ...bird }; console.log(birdClone); // => { type: 'pigeon', color: 'white' } console.log(bird === birdClone); // => false
.bird
在字符符上复制了bird
自己和可枚举的属性,并传给了birdClone
目标。因此birdClone
是bird
的克隆。
虽然克隆对象技术乍一看似乎很简单,但有一些细节的差异还是需要注意的。
浅拷贝
Object Spread只会做一个对象的浅拷贝。只有对象本身是克隆的,而嵌套的实例不是克隆的。
laptop
有一个嵌套的对象screen
。如果克隆laptop
对象,看看对其嵌套的对象有何影响:
const laptop = { name: 'MacBook Pro', screen: { size: 17, isRetina: true } };const laptopClone = { ...laptop }; console.log(laptop === laptopClone); // => false console.log(laptop.screen === laptopClone.screen); // => true
首先比较laptop === laptopClone
,其值是false
。主对象被正确克隆。
然而,laptop.screen === laptopClone.screen
值是true
。这意味着,laptop.screen
和laptopClone.screen
引用相同的嵌套对象,但没有复制。
其实,你可以在任何级别上做传播。只需稍加努力,就可以克隆嵌套的对象:
const laptopDeepClone = { ...laptop, screen: { ...laptop.screen } }; console.log(laptop === laptopDeepClone); // => false console.log(laptop.screen === laptopDeepClone.screen); // => false
一个额外的...laptop.screen
就确保了嵌套对象也被克隆了。现在,laptopDeepClone
完整的克隆了laptop
对象。
原型丢失
下面的代码片段声明了一个Game
的类,并用这个类创建了一个例实例doom
:
class Game { constructor(name) { this.name = name; } getMessage() { return `I like ${this.name}!`; } }const doom = new Game('Doom'); console.log(doom instanceof Game); // => true console.log(doom.name); // => "Doom" console.log(doom.getMessage()); // => "I like Doom!"
现在,让我们来克隆一个调用构造函数创建的doom
实例。这可能会给你带来一个惊喜:
const doomClone = { ...doom }; console.log(doomClone instanceof Game); // => false console.log(doomClone.name); // => "Doom" console.log(doomClone.getMessage()); // => TypeError: doomClone.getMessage is not a function
...doom
只将自己的属性name
复制到doomClone
而已。
doomClone
是一个普通的JavaScript对象,其原型是Object.prototype
,而不是Game.prototype
,这是可以预期的。Object Spread不保存源对象的原型。
因此,调用doomClone.getMessage()
会抛出一个TypeError
错误,那是因为doomClone
不会继承getMessage()
方法。
要修复丢失的原型,需要手动使用__proto__
:
const doomFullClone = { ...doom, __proto__: Game.prototype }; console.log(doomFullClone instanceof Game); // => true console.log(doomFullClone.name); // => "Doom" console.log(doomFullClone.getMessage()); // => "I like Doom!"
对象字面符上的__proto__
确保了doomFullClone
有Game.prototype
原型。
不赞成使用
__proto__
,这里只是用来做演示。
对象传播滞后于调用构造函数创建的实例,因为它不保存原型。其意图是用来浅拷贝源对象自己和可枚举的属性。因此忽略原型的方法似乎也是合理的。
顺便说一下,使用Object.assign()
可以更合理的克隆doom
:
const doomFullClone = Object.assign(new Game(), doom); console.log(doomFullClone instanceof Game); // => true console.log(doomFullClone.name); // => "Doom" console.log(doomFullClone.getMessage()); // => "I like Doom!"
我保证,使用这种方法,原型也会克隆过来。
更新不可变对象
当一个对象在应用程序的多个地方共用时,直接修改这个对象可能会带来意想不到的副作用。而且跟踪这些修改也是极为蛋疼的事情。
更好的方法是使用操作不可变。不可变能更好的控制对象的修改,有利于编写纯函数。即使在一些复杂的场景中,也更容易确定对象更新的源和原因,因为数据流到一个单一的方向。
Object Spread方便以不可变的方式来修改对象。所设你有一个对象描述了一本书的版本信息:
const book = { name: 'JavaScript: The Definitive Guide', author: 'David Flanagan', edition: 5, year: 2008};
然后这本书的第六版本出来了。Object Spread让我们可以以不可变的方式对这个场景进行编程:
const newerBook = { ...book, edition: 6, // <----- Overwrites book.edition year: 2011 // <----- Overwrites book.year}; console.log(newerBook);
...book
复制了book
对象的属性。然后手动添加edition:6
和year:2011
来更新属性值。
在最后指定重要的属性值,因为相同的键值,后面的会覆盖前面的。
newBook
是一个具有更新属性的新对象。与此同时,原来的book
对象仍然完好无损。达到我们的不变性需求。
合并对象
合并对象很简单,因为你可以扩展对象任意数量的属性。
让我们来合并三个对象:
const part1 = { color: 'white'};const part2 = { model: 'Honda'};const part3 = { year: 2005};const car = { ...part1, ...part2, ...part3 }; console.log(car); // { color: 'white', model: 'Honda', year: 2005 }
car
对象的创建是由part1
、part2
和part3
三个对象合并而来。
不要忘记,后者会覆盖前者的规则。它给出了合并多个具有相同键对象的理由。
让我们改变一下前面的例子。现在part1
和part3
具有一个新的属性configuration
:
const part1 = { color: 'white', configuration: 'sedan'};const part2 = { model: 'Honda'};const part3 = { year: 2005, configuration: 'hatchback'};const car = { ...part1, ...part2, ...part3 // <--- part3.configuration overwrites part1.configuration}; console.log(car);
首先...part1
设置了configuration
的值为sedan
,但是后面的...part3
的configuration
设置的hatchback
覆盖了前面的。
使用默认值填充对象
对象可以在运行时拥有不同的属性集。一些属性可以被设置,另一些可能会丢失。
这种情况可能发生在配置对象的情况下。用户只能指定配置的重要属性,但未能指从默认中提取的属性。
让我们实现一个multiline(str, config)
函数,通过给定的宽度,将 str
分成多个行。
config
对象可能会接受下面几个参数:
width
:要断开的字符数。默认为10
newLine
:在行尾添加字符串。默认为\n
indent
:打算的行。默认值为' '
multiline()
函数几个示例:
multiline('Hello World!'); // => 'Hello Worl\nd!'multiline('Hello World!', { width: 6 }); // => 'Hello \nWorld!'multiline('Hello World!', { width: 6, newLine: '*' }); // => 'Hello *World!'multiline('Hello World!', { width: 6, newLine: '*', indent: '_' }); // => '_Hello *_World!'
config
参数接受不同的属性集:你可以表示1
,2
或3
个属性,甚至没有属性。
使用Object Spread会非常简单,可以用默认值填充config
对象。在对象字面符中首先展开默认对象,然后是config
对象:
function multiline(str, config = {}) { const defaultConfig = { width: 10, newLine: '\n', indent: '' }; const safeConfig = { ...defaultConfig, ...config }; let result = ''; // Implementation of multiline() using // safeConfig.width, safeConfig.newLine, safeConfig.indent // ... return result; }
让我们管理safeConfig
对象字面量。
...defaultConfig
从默认值中提取属性,然后...config
配置将会覆盖以前的默认值和自定义属性值。
因此,safeConfig
具有multiline()
函数可以使用的全部属性。无论输入的配置是否会遗漏一些属性,safeConfig
都会具备必要的值。
Object Spread能非常直观的使用默认值。
我们需要更进一步
Object Spread非常酷的地方在于可以在嵌套对象上使用。当更新一个大对象时,这是一个很好的优势,具有很好的可读性。但还是推荐使用Object.assign()
来替代。
下面的box
对象定义了box
的标签:
const box = { color: 'red', size: { width: 200, height: 100 }, items: ['pencil', 'notebook'] };
box.size
描述了box
的尺寸,以及box.items
中包含了box
中可枚举的item
。
通过增加box.size.height
使box
变高。只需要在嵌套对象上扩展height
属性:
const biggerBox = { ...box, size: { ...box.size, height: 200 } }; console.log(biggerBox);
...box
可以确保biggerBox
接收来自box
源的属性。
更新嵌套对象box.size
的height
,只需要额外的一个对象字面量{...box.size, height:200}
。这样一来,box.size
的height
属性就得到了一个新值,其值更新为200
。
我喜欢通过一个语句执行多个更新的可能性。
如何将颜色改为黑色,将宽度增加到400
,并添加一个新的项目ruler
到items
中(使用扩展数组)?这很简单:
const blackBox = { ...box, color: 'black', size: { ...box.size, width: 400 }, items: [ ...box.items, 'ruler' ] }; console.log(blackBox);
传播undefined
、null
和原始值
当扩展undefined
、null
或原始值时,不会提取任何属性,也不会抛出任何错误。中会返回一个空的对象:
const nothing = undefined; const missingObject = null; const two = 2; console.log({ ...nothing }); // => { } console.log({ ...missingObject }); // => { } console.log({ ...two }); // => { }
从nothing
、missingObject
和two
中,Object Spread没有扩展任何属性。当然,没有理由在原始值上使用Object Spread。
Object rest属性
使用结构赋值将对象的属性提取到变量之后,剩余的属性可以被收集到rest对象中。
这就是对象rest属性的好处:
const style = { width: 300, marginLeft: 10, marginRight: 30};const { width, ...margin } = style; console.log(width); // => 300 console.log(margin); // => { marginLeft: 10, marginRight: 30 }
使用结构性赋值定义了一个新的变量width
,并将其值设置为style.width
。...margin
只会收集marginLeft
和marginRight
属性。
Object rest只收集自己的和可枚举的属性。
注意,Object rest必须是结构性赋值中的最后一个元素。因此const { ...margin , width } = style
将会报错:SyntaxError: Rest element must be last element
。
总结
Object spread有一些规则要记住:
它从源对象中提取自己的和可枚举的属性
扩展的属性具有相同键的,后者会覆盖前者
与此同时,Object spread是简短而且富有表现力的,同时在嵌套对象上也能很好的工作,同时也保持更新的不变性。它可以轻松的实现对象克隆、合并和填充默认属性。
在结构性赋值中使用Object rest语法,可以收集剩余的属性。
实际上,Object rest和Object spread是JavaScript的重要补充。
相关推荐: