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

在JavaScript中合并多个对象是一个很常见的事情。但在JavaScript中,到目前为止并没有一种很方便的语法来进行合并。本文主要和大家分享三个点如何改变JavaScript对象的restspread属性。

在ES5中,通过使用Lodash的_.extend(target, [sources])(或者其他选项),在ES2015中引入了Object.assign(target, [sources])。

幸运的是,对象的spread语法(ECMAScript第3阶段建议)是如何操作对象,提供了一个简短且易于遵循的语法。

const cat = {      legs: 4,    sound: 'meow'};const dog = {      ...cat,    sound: 'woof'};console.log(dog);

登录后复制

1.png

在上面的例子中,…cat将cat对象复制到一个新的dog对象中。.sound属性的值woof放在最后面。

立即学习“Java免费学习笔记(深入)”;

这篇文章我们来了解对象的rest和spread语法。来看如何实现对象的克隆、合并以及属性重写等。

下面对可枚举属性简短的概括,以及如何区分自己与继承属性。这些是理解对象如何spread和rest语法的必要基础。

枚举和自己属性

在JavaScript中,对象是键(key)和值(value)之间的关联。

key的类型通常是string或symbol。值可以是一个基本类型(string、boolean、number、undefined或null),也可以是一个object或function。

下面的对象使用了对象字面符:

const person = {    name: 'Dave',    surname: 'Bowman'}

登录后复制

person对象描述了一个人的name和surname。

枚举属性

属性(property)有几个属性(attribute)来描述值,还有可写的、可枚举的和可配置的状态。有关于这方面更多的细节,请参阅JavaScript中的对象属性。

可枚举属性是一个布尔值,它指的是在枚举对象属性是否可访问。

可以使用Object.keys()来枚举对象属性(有于访问自己的和可枚举的属性)。也可以使用for…in语句(枚举所有可枚举属性),等等。

对象字面值声明的属性{prop1: ‘val1′, prop2:’val2’}是可枚举的。让我们看看person对象包含的可枚举的属性:

const keys = Object.keys(person);  console.log(keys);

登录后复制

2.png

name和surname是person对象可枚举的属性。

有趣的部分来了。Object spread从源可枚举属性中克隆过来:

console.log({ ...person })

登录后复制

3.png

现在我们给person对象创建一个不可枚举的属性age。然后再看看Object的spread行为:

Object.defineProperty(person, 'age', {      enumerable: false, // 属性不可枚举    value: 25});console.log(person['age']); // => 25const clone = {      ...person};console.log(clone);

登录后复制

4.png

name和surname可枚举属从源对象person复制到clone对象中,但是不可枚举属性age并没有被复制进去。

自己的属性

JavaScript原型可以继承。因此对象属性可是以自己的,也可以是继承的。

对象字面符显式声明的属性是自己的,但是对象从原型接收的属性是继承的。

接下来创建一个personB对象,并且设置它的属性为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

登录后复制

personB对象有一个自己的属性profession和从person属性中继承过来的name和surname属性。

Object spread从自己的源属性中进行复制的时候,会忽略继承的属性。

const cloneB = {      ...personB};console.log(cloneB); // => { profession: 'Astronaut' }

登录后复制

…personB对象从源对象personB中仅复制了自己的属性profession,但忽略了可继承的name和surname属性。

Object spread可以从源对象中复制自己的和可枚举的属性。和Object.keys()相同。

传播对象属性

对象字符符中里的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' //  { sound: 'woof', legs: 4 }

登录后复制

后面的属性值woof覆盖了前面的属性值meow(来自cat对象的sound的值)。这符合使用相同的键值时,后一个属性值将覆盖最早的属性值的规则。

同样的规则也适用于对象初始化的规则属性:

const anotherDog = {      ...cat,    sound: 'woof' //  { sound: 'woof', legs: 4 }

登录后复制

sound: ‘woof’规则最终获胜,那是因为他在最后。

现在如果你交换传播对象的相对位置,结果是不同的:

const stillCat = {      ...{        sound: 'woof' //  { 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对象。

5.png

原型丢失

下面的代码片段声明了一个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,  // 

6.png

...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 // 

7.png

首先...part1设置了configuration的值为sedan,但是后面的...part3的configuration设置的hatchback覆盖了前面的。

使用默认值填充对象

对象可以在运行时拥有不同的属性集。一些属性可以被设置,另一些可能会丢失。

这种情况可能发生在配置对象的情况下。用户只能指定配置的重要属性,但未能指从默认中提取的属性。

让我们实现一个multiline(str, config)函数,通过给定的宽度,将 str分成多个行。

config对象可能会接受下面几个参数:

登录后复制

width:要断开的字符数。默认为10

newLine:在行尾添加字符串。默认为

indent:打算的行。默认值为’ ‘

multiline()函数几个示例:

multiline('Hello World!');  // => 'Hello World!'multiline('Hello World!', { width: 6 });  // => 'Hello World!'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: '',        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);

登录后复制

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

…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);

登录后复制

8.png

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

相关推荐:

了解JavaScript,函数中的 Rest 参数

以上就是改变JavaScript对象的rest和spread属性方法的详细内容,更多请关注【创想鸟】其它相关文章!

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至253000106@qq.com举报,一经查实,本站将立刻删除。

发布者:PHP中文网,转转请注明出处:https://www.chuangxiangniao.com/p/2786610.html

(0)
上一篇 2025年3月8日 17:47:16
下一篇 2025年3月8日 17:47:39

AD推荐 黄金广告位招租... 更多推荐

相关推荐

  • Vue.js响应式原理详解

    本人是Java背景,许多年前刚接触JavaScript时有点怪怪的,因为它没有 getters 和 setters。随着时间的推移,我开始喜欢上这个缺失的特性,因为相比Java大量的 getter 和 setter,它让代码更简洁。例如,我…

    2025年3月8日
    200
  • javascript中的隐式调用详解

    所谓的隐式调用简单来说就是自动调用一些方法,而这些方法像钩子一样可以在外部修改,从而改变既定行为。 下面我会列举一些最近看到的隐式调用,例子都是点到即止,欢迎补充 数据类型转换 toSting 和 valueOf var obj = { a…

    编程技术 2025年3月8日
    200
  • js之trim函数实现删除两端空格

    本文主要和大家介绍了js自定义trim函数实现删除两端空格功能,结合实例形式分析了javascript基于正则替换实现类似trim函数删除字符串两端空格的相关操作技巧,希望能帮助到大家。 兼容IE低版本浏览器,以及其他一些低版本脚本的浏览器…

    编程技术 2025年3月8日
    200
  • 详解JavaScript的运行原理

    本文主要给大家从理论详细分析了javascript运行原理以及知识点分享,对此有兴趣的学习下吧。 JavaScript是一种基于对象的动态、弱类型脚本语言(以下简称JS),是一种解释型语言,和其他的编程语言不同,如java/C++等编译型语…

    编程技术 2025年3月8日
    200
  • JavaScript比较同一天的时间大小

    在项目开发的过程中,有时候会遇到同一天内的时间大小比较,一般来说选择时间是通过插件实现的,但并不排除客户要求手动输入的情况。那么,在应客户要求手动输入时间,并且需要进行时间大小比较的时候该如何实现呢?以下简单介绍我实现的方法步骤: 1.首先…

    编程技术 2025年3月8日
    200
  • js正则表达式验证时间格式实例

    在项目中,我们经常会遇到时间问题,有时候是使用时间插件,让用户选择,但有时应客户要求,能够自行输入时间,那么,我们如何来确定用户输入的时间格式是否正确,输入的时间是否合法呢?这个时候需要用到正则表达式。本文主要和大家介绍了详解js正则表达式…

    编程技术 2025年3月8日
    200
  • JavaScript事件解析

    事件是在编程时系统内发生的动作或者发生的事情.系统通过它来告诉编程者,在编程者愿意的情况下,编程者一某种方式对它做出回应.本文主要和大家分享JavaScript事件解析,希望能帮助到大家。 添加事件方式 元素属性 var btn = doc…

    2025年3月8日
    200
  • JS实现图片轮播的实例

    本文主要为大家带来一篇使用js实现图片轮播的实例(前后首尾相接),代码参考了一位已经写好了图片轮播功能的(再次表示感谢),但是没有首尾相接的功能,本人在此基础上增加了首尾相接功能。 效果如下: nbsp;html PUBLIC “-//W3…

    2025年3月8日
    200
  • JavaScript数组进化与性能分析实例

    在使用 javascript 前,我对 c、c++、c# 这些已经颇为熟悉。与许多 c/c++ 开发者一样,javascript 给我的第一印象并不好。本文主要和大家介绍了javascript 数组的进化与性能分析,本文讲得更多的是内存、优…

    2025年3月8日
    200
  • js高效率服务器时间同步实例

    本文主要和大家分享一个js倒计时的功能代码,首先说一下,为什么要服务器时间同步, 因为服务器时间和本地电脑时间存在一定的时间差。有些对时效性要求非常高的应用,例如时时彩开奖,是不能容忍这种时间差存在的。 方案1:每次倒计时去服务端请求时间 …

    编程技术 2025年3月8日
    200

发表回复

登录后才能评论