最新文章专题视频专题问答1问答10问答100问答1000问答2000关键字专题1关键字专题50关键字专题500关键字专题1500TAG最新视频文章推荐1 推荐3 推荐5 推荐7 推荐9 推荐11 推荐13 推荐15 推荐17 推荐19 推荐21 推荐23 推荐25 推荐27 推荐29 推荐31 推荐33 推荐35 推荐37视频文章20视频文章30视频文章40视频文章50视频文章60 视频文章70视频文章80视频文章90视频文章100视频文章120视频文章140 视频2关键字专题关键字专题tag2tag3文章专题文章专题2文章索引1文章索引2文章索引3文章索引4文章索引5123456789101112131415文章专题3
当前位置: 首页 - 科技 - 知识百科 - 正文

改变JavaScript对象的rest和spread属性方法

来源:动视网 责编:小采 时间:2020-11-27 20:03:32
文档

改变JavaScript对象的rest和spread属性方法

改变JavaScript对象的rest和spread属性方法:在JavaScript中合并多个对象是一个很常见的事情。但在JavaScript中,到目前为止并没有一种很方便的语法来进行合并。本文主要和大家分享三个点如何改变JavaScript对象的rest和spread属性。在ES5中,通过使用Lodash的_.extend(target, [s
推荐度:
导读改变JavaScript对象的rest和spread属性方法:在JavaScript中合并多个对象是一个很常见的事情。但在JavaScript中,到目前为止并没有一种很方便的语法来进行合并。本文主要和大家分享三个点如何改变JavaScript对象的rest和spread属性。在ES5中,通过使用Lodash的_.extend(target, [s


传播对象属性

对象字符符中里的Object spread可以复制源对象自己的和可枚举的属性,并将其复制到目标对象中。

const targetObject = { 
 ...sourceObject,
 property: 'Value'};

顺便说一下,在许多方面,Object spread相当于Object.assign()。上面的代码也可以这样实现:

const targetObject = Object.assign( 
 { }, 
 sourceObject,
 { property: 'Value' }
);

一个对象字面符可以有多个Object spread,任何组合都可以使用规则属性声明:

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目标。因此birdClonebird的克隆。

虽然克隆对象技术乍一看似乎很简单,但有一些细节的差异还是需要注意的。

浅拷贝

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.screenlaptopClone.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__确保了doomFullCloneGame.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:6year: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对象的创建是由part1part2part3三个对象合并而来。

不要忘记,后者会覆盖前者的规则。它给出了合并多个具有相同键对象的理由。

让我们改变一下前面的例子。现在part1part3具有一个新的属性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,但是后面的...part3configuration设置的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参数接受不同的属性集:你可以表示123个属性,甚至没有属性。

    使用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.sizeheight,只需要额外的一个对象字面量{...box.size, height:200}。这样一来,box.sizeheight属性就得到了一个新值,其值更新为200

    我喜欢通过一个语句执行多个更新的可能性。

    如何将颜色改为黑色,将宽度增加到400,并添加一个新的项目ruleritems中(使用扩展数组)?这很简单:

    const blackBox = { 
     ...box,
     color: 'black',
     size: {
     ...box.size,
     width: 400
     },
     items: [
     ...box.items, 'ruler'
     ]
    };
    console.log(blackBox);

    传播undefinednull和原始值

    当扩展undefinednull或原始值时,不会提取任何属性,也不会抛出任何错误。中会返回一个空的对象:

    const nothing = undefined; 
    const missingObject = null; 
    const two = 2;
    
    console.log({ ...nothing }); // => { } console.log({ ...missingObject }); // => { } console.log({ ...two }); // => { }

    nothingmissingObjecttwo中,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只会收集marginLeftmarginRight属性。

    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的重要补充。

    文档

    改变JavaScript对象的rest和spread属性方法

    改变JavaScript对象的rest和spread属性方法:在JavaScript中合并多个对象是一个很常见的事情。但在JavaScript中,到目前为止并没有一种很方便的语法来进行合并。本文主要和大家分享三个点如何改变JavaScript对象的rest和spread属性。在ES5中,通过使用Lodash的_.extend(target, [s
    推荐度:
    • 热门焦点

    最新推荐

    猜你喜欢

    热门推荐

    专题
    Top