搜索
首页web前端js教程改变JavaScript对象的rest和spread属性方法

在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

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

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

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

枚举和自己属性

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

key的类型通常是stringsymbol。值可以是一个基本类型(stringbooleannumberundefinednull),也可以是一个objectfunction

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

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

person对象描述了一个人的namesurname

枚举属性

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

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

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

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

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

2.png

namesurnameperson对象可枚举的属性。

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

console.log({ ...person })

3.png

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

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

4.png

namesurname可枚举属从源对象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属性中继承过来的namesurname属性。

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

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

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

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: &#39;woof&#39; // <----- 覆盖 cat.sound
    }
};console.log(dog);  // => { sound: &#39;woof&#39;, legs: 4 }

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

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

const anotherDog = {  
    ...cat,
    sound: &#39;woof&#39; // <---- Overwrites cat.sound};console.log(anotherDog); // => { sound: &#39;woof&#39;, legs: 4 }

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

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

const stillCat = {  
    ...{
        sound: &#39;woof&#39; // <---- Is overwritten by cat.sound
    },    ...cat};console.log(stillCat); // => { sound: &#39;meow&#39;, legs: 4 }

猫仍然是猫。尽管第一个源对象提供了sound属性的值为woof,但它还是被后面的cat对象的sound的属性值meow覆盖了。

Object spread的位置和正则性质很重要。这种语法允许实现诸如对象克隆、合并和填充默认值之类的。

下面我们来看看。

对象克隆

使用Object Spread语法可以用一个简短而富有表现力的方式来克隆一个对象。下面的例子克隆了bird对象:

const bird = {  
    type: &#39;pigeon&#39;,
    color: &#39;white&#39;};const birdClone = {  
    ...bird
};

console.log(birdClone); // => { type: &#39;pigeon&#39;, color: &#39;white&#39; }  console.log(bird === birdClone); // => false

.bird在字符符上复制了bird自己和可枚举的属性,并传给了birdClone目标。因此birdClonebird的克隆。

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

浅拷贝

Object Spread只会做一个对象的浅拷贝。只有对象本身是克隆的,而嵌套的实例不是克隆的。

laptop有一个嵌套的对象screen。如果克隆laptop对象,看看对其嵌套的对象有何影响:

const laptop = {  
    name: &#39;MacBook Pro&#39;,
    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对象。

5.png

原型丢失

下面的代码片段声明了一个Game的类,并用这个类创建了一个例实例doom

class Game {  
    constructor(name) {        this.name = name;
    }

    getMessage() {        return `I like ${this.name}!`;
    }
}const doom = new Game(&#39;Doom&#39;);  
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: &#39;JavaScript: The Definitive Guide&#39;,
    author: &#39;David Flanagan&#39;,
    edition: 5,
    year: 2008};

然后这本书的第六版本出来了。Object Spread让我们可以以不可变的方式对这个场景进行编程:

const newerBook = {  
    ...book,
    edition: 6,  // <----- Overwrites book.edition
    year: 2011   // <----- Overwrites book.year};

console.log(newerBook);

6.png

...book复制了book对象的属性。然后手动添加edition:6year:2011来更新属性值。

在最后指定重要的属性值,因为相同的键值,后面的会覆盖前面的。

newBook是一个具有更新属性的新对象。与此同时,原来的book对象仍然完好无损。达到我们的不变性需求。

合并对象

合并对象很简单,因为你可以扩展对象任意数量的属性。

让我们来合并三个对象:

const part1 = {  
    color: &#39;white&#39;};const part2 = {  
    model: &#39;Honda&#39;};const part3 = {  
    year: 2005};const car = {  
    ...part1,
    ...part2,
    ...part3
};
console.log(car); // { color: &#39;white&#39;, model: &#39;Honda&#39;, year: 2005 }

car对象的创建是由part1part2part3三个对象合并而来。

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

让我们改变一下前面的例子。现在part1part3具有一个新的属性configuration

const part1 = {  
    color: &#39;white&#39;,
    configuration: &#39;sedan&#39;};const part2 = {  
    model: &#39;Honda&#39;};const part3 = {  
    year: 2005,
    configuration: &#39;hatchback&#39;};const car = {  
    ...part1,
    ...part2,
    ...part3 // <--- part3.configuration overwrites part1.configuration};
console.log(car);

7.png

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

使用默认值填充对象

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

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

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

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

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

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

  • indent:打算的行。默认值为' '

multiline()函数几个示例:

multiline(&#39;Hello World!&#39;);  
// => &#39;Hello Worl\nd!&#39;multiline(&#39;Hello World!&#39;, { width: 6 });  
// => &#39;Hello \nWorld!&#39;multiline(&#39;Hello World!&#39;, { width: 6, newLine: &#39;*&#39; });  
// => &#39;Hello *World!&#39;multiline(&#39;Hello World!&#39;, { width: 6, newLine: &#39;*&#39;, indent: &#39;_&#39; });  
// => &#39;_Hello *_World!&#39;

config参数接受不同的属性集:你可以表示123个属性,甚至没有属性。

使用Object Spread会非常简单,可以用默认值填充config对象。在对象字面符中首先展开默认对象,然后是config对象:

function multiline(str, config = {}) {  
    const defaultConfig = {
        width: 10,
        newLine: &#39;\n&#39;,
        indent: &#39;&#39;
    };    const safeConfig = {
        ...defaultConfig,
        ...config
    };    let result = &#39;&#39;;    // 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: &#39;red&#39;,
    size: {
        width: 200, 
        height: 100 
    },
    items: [&#39;pencil&#39;, &#39;notebook&#39;]
};

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: &#39;black&#39;,
    size: {
        ...box.size,
        width: 400
    },
    items: [
        ...box.items,        &#39;ruler&#39;
    ]
};
console.log(blackBox);

8.png

传播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 参数

以上是改变JavaScript对象的rest和spread属性方法的详细内容。更多信息请关注PHP中文网其他相关文章!

声明
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn
Python和JavaScript的未来:趋势和预测Python和JavaScript的未来:趋势和预测Apr 27, 2025 am 12:21 AM

Python和JavaScript的未来趋势包括:1.Python将巩固在科学计算和AI领域的地位,2.JavaScript将推动Web技术发展,3.跨平台开发将成为热门,4.性能优化将是重点。两者都将继续在各自领域扩展应用场景,并在性能上有更多突破。

Python vs. JavaScript:开发环境和工具Python vs. JavaScript:开发环境和工具Apr 26, 2025 am 12:09 AM

Python和JavaScript在开发环境上的选择都很重要。1)Python的开发环境包括PyCharm、JupyterNotebook和Anaconda,适合数据科学和快速原型开发。2)JavaScript的开发环境包括Node.js、VSCode和Webpack,适用于前端和后端开发。根据项目需求选择合适的工具可以提高开发效率和项目成功率。

JavaScript是用C编写的吗?检查证据JavaScript是用C编写的吗?检查证据Apr 25, 2025 am 12:15 AM

是的,JavaScript的引擎核心是用C语言编写的。1)C语言提供了高效性能和底层控制,适合JavaScript引擎的开发。2)以V8引擎为例,其核心用C 编写,结合了C的效率和面向对象特性。3)JavaScript引擎的工作原理包括解析、编译和执行,C语言在这些过程中发挥关键作用。

JavaScript的角色:使网络交互和动态JavaScript的角色:使网络交互和动态Apr 24, 2025 am 12:12 AM

JavaScript是现代网站的核心,因为它增强了网页的交互性和动态性。1)它允许在不刷新页面的情况下改变内容,2)通过DOMAPI操作网页,3)支持复杂的交互效果如动画和拖放,4)优化性能和最佳实践提高用户体验。

C和JavaScript:连接解释C和JavaScript:连接解释Apr 23, 2025 am 12:07 AM

C 和JavaScript通过WebAssembly实现互操作性。1)C 代码编译成WebAssembly模块,引入到JavaScript环境中,增强计算能力。2)在游戏开发中,C 处理物理引擎和图形渲染,JavaScript负责游戏逻辑和用户界面。

从网站到应用程序:JavaScript的不同应用从网站到应用程序:JavaScript的不同应用Apr 22, 2025 am 12:02 AM

JavaScript在网站、移动应用、桌面应用和服务器端编程中均有广泛应用。1)在网站开发中,JavaScript与HTML、CSS一起操作DOM,实现动态效果,并支持如jQuery、React等框架。2)通过ReactNative和Ionic,JavaScript用于开发跨平台移动应用。3)Electron框架使JavaScript能构建桌面应用。4)Node.js让JavaScript在服务器端运行,支持高并发请求。

Python vs. JavaScript:比较用例和应用程序Python vs. JavaScript:比较用例和应用程序Apr 21, 2025 am 12:01 AM

Python更适合数据科学和自动化,JavaScript更适合前端和全栈开发。1.Python在数据科学和机器学习中表现出色,使用NumPy、Pandas等库进行数据处理和建模。2.Python在自动化和脚本编写方面简洁高效。3.JavaScript在前端开发中不可或缺,用于构建动态网页和单页面应用。4.JavaScript通过Node.js在后端开发中发挥作用,支持全栈开发。

C/C在JavaScript口译员和编译器中的作用C/C在JavaScript口译员和编译器中的作用Apr 20, 2025 am 12:01 AM

C和C 在JavaScript引擎中扮演了至关重要的角色,主要用于实现解释器和JIT编译器。 1)C 用于解析JavaScript源码并生成抽象语法树。 2)C 负责生成和执行字节码。 3)C 实现JIT编译器,在运行时优化和编译热点代码,显着提高JavaScript的执行效率。

See all articles

热AI工具

Undresser.AI Undress

Undresser.AI Undress

人工智能驱动的应用程序,用于创建逼真的裸体照片

AI Clothes Remover

AI Clothes Remover

用于从照片中去除衣服的在线人工智能工具。

Undress AI Tool

Undress AI Tool

免费脱衣服图片

Clothoff.io

Clothoff.io

AI脱衣机

Video Face Swap

Video Face Swap

使用我们完全免费的人工智能换脸工具轻松在任何视频中换脸!

热工具

Atom编辑器mac版下载

Atom编辑器mac版下载

最流行的的开源编辑器

Dreamweaver CS6

Dreamweaver CS6

视觉化网页开发工具

EditPlus 中文破解版

EditPlus 中文破解版

体积小,语法高亮,不支持代码提示功能

Dreamweaver Mac版

Dreamweaver Mac版

视觉化网页开发工具

SublimeText3 英文版

SublimeText3 英文版

推荐:为Win版本,支持代码提示!