Object.assign方法详解及使用示例
时间:2025-07-16 14:18:35 127浏览 收藏
从现在开始,努力学习吧!本文《JavaScript中Object.assign方法用于将一个或多个源对象的属性复制到目标对象中。它返回目标对象,如果目标对象存在同名属性,则会被覆盖。语法:Object.assign(target, ...sources)参数说明:target:目标对象,属性将被复制到此对象。sources:一个或多个源对象,其属性将被复制到目标对象。示例:示例1:简单复制const target = { a: 1 }; const source = { b: 2 }; const result = Object.assign(target, source); console.log(result); // { a: 1, b: 2 }示例2:覆盖属性const target = { a: 1 }; const source1 = { a: 2 }; const source2 = { b: 3 }; const result = Object.assign(target, source1, source2); console.log(result); // { a: 2, b: 3 }示例3:浅拷贝const target = { a: { x: 1 } }; const source = { a: { y: 2 } }; const result = Object.assign(target, source); console.log(result.a); // { y: 2 } // 注意:这里只是引用了对象,而不是深拷贝注意事项:`》主要讲解了等等相关知识点,我会在golang学习网中持续更新相关的系列文章,欢迎大家关注并积极留言建议。下面就先一起来看一下本篇正文内容吧,希望能帮到你!
Object.assign是JavaScript中用于复制源对象可枚举自有属性到目标对象的方法,返回目标对象。1. 它支持合并多个源对象,同名属性后覆盖前;2. 可用于克隆对象(浅拷贝)、设置默认值、混入功能等场景;3. 仅复制自有且可枚举属性,不复制原型链或不可枚举属性;4. 处理访问器属性时会调用getter并复制其返回值,而非保留getter/setter;5. 目标为原始类型时会被包装成对象,null/undefined源对象被忽略;6. 执行的是浅拷贝,嵌套引用类型修改会影响原对象;7. 如需深拷贝应使用JSON.parse(JSON.stringify())、递归拷贝或第三方库如Lodash的cloneDeep。使用时应注意属性覆盖顺序、默认值优先级及潜在副作用。
JavaScript的Object.assign
方法,说白了,就是一个用来把一个或多个源对象的可枚举自有属性复制到目标对象身上的工具。它会返回目标对象本身。这个方法在处理对象合并、配置默认值或是做浅拷贝的时候,用起来非常顺手。

解决方案
Object.assign
的用法很简单,它的基本语法是Object.assign(target, ...sources)
。
target
是你的目标对象,所有源对象的属性都会复制到它上面。如果目标对象不是一个对象(比如是null
、undefined
、字符串、数字或布尔值),它会被强制转换为一个对象。

sources
是一个或多个源对象,它们的属性会被复制到目标对象。复制是按照源对象在参数列表中的顺序进行的,如果多个源对象有同名属性,后面的属性会覆盖前面的。
来看几个例子:

1. 合并对象
const obj1 = { a: 1, b: 2 }; const obj2 = { b: 3, c: 4 }; const mergedObj = Object.assign({}, obj1, obj2); console.log(mergedObj); // { a: 1, b: 3, c: 4 } // 注意:b的值被obj2覆盖了
这里我们用一个空对象{}
作为目标对象,这样就不会修改到obj1
或obj2
。这其实也是一种创建新对象并合并属性的常见方式。
2. 给现有对象添加或更新属性
const user = { name: '张三', age: 25 }; const updates = { age: 26, city: '北京' }; Object.assign(user, updates); console.log(user); // { name: '张三', age: 26, city: '北京' } // user对象本身被修改了
3. 处理非对象目标或源
如果目标是原始类型,它会被包装成对象。但源对象如果是null
或undefined
,它们会被跳过,因为它们没有可枚举的自有属性。
let num = 123; let result = Object.assign(num, { a: 1 }); console.log(result); // [Number: 123] (一个Number对象,但并没有复制属性,因为num作为目标时会被包装,但复制操作本身不会改变原始值) const sourceNull = null; const sourceUndefined = undefined; const obj = {}; Object.assign(obj, sourceNull, { x: 1 }, sourceUndefined, { y: 2 }); console.log(obj); // { x: 1, y: 2 } // null和undefined源被忽略了
Object.assign
是深拷贝还是浅拷贝?这对我的数据有什么影响?
这是一个非常关键的问题,也是很多初学者容易掉进的“坑”。答案是:Object.assign
执行的是浅拷贝。
这意味着什么呢?简单来说,当它复制一个对象的属性时:
- 如果属性的值是原始类型(比如字符串、数字、布尔值、
null
、undefined
或symbol
),那么这个值会被直接复制过去,就好像你把一个数字从一个变量赋给另一个变量一样。 - 但如果属性的值是一个引用类型(比如另一个对象、数组或函数),
Object.assign
复制的不是这个引用类型的值本身,而是它在内存中的引用地址。
用个例子来说明可能更直观:
const original = { name: 'Alice', info: { age: 30, city: 'New York' }, hobbies: ['reading', 'coding'] }; const copied = Object.assign({}, original); console.log(copied); // { // name: 'Alice', // info: { age: 30, city: 'New York' }, // hobbies: ['reading', 'coding'] // } // 现在,我们尝试修改copied对象中的嵌套属性 copied.name = 'Bob'; // 原始类型,互不影响 copied.info.age = 31; // 引用类型,修改会影响original copied.hobbies.push('hiking'); // 引用类型,修改会影响original console.log('--- 修改后 ---'); console.log('Original:', original); // Original: { // name: 'Alice', // info: { age: 31, city: 'New York' }, // 注意:age被修改了 // hobbies: ['reading', 'coding', 'hiking'] // 注意:hiking被添加了 // } console.log('Copied:', copied); // Copied: { // name: 'Bob', // info: { age: 31, city: 'New York' }, // hobbies: ['reading', 'coding', 'hiking'] // }
从上面的输出你可以清楚地看到,copied.name
的修改没有影响original.name
,因为'Alice'
是原始值。但copied.info.age
和copied.hobbies
的修改却直接影响了original
对象对应的属性。这是因为original.info
和copied.info
指向的是内存中的同一个对象,original.hobbies
和copied.hobbies
指向的也是同一个数组。
这对你的数据意味着什么?
- 方便性与风险并存: 浅拷贝在很多场景下已经足够用,比如你只是想合并一些扁平的配置对象。它效率高,也符合预期。
- 意外的副作用: 如果你的对象里有嵌套的对象或数组,并且你希望拷贝后的对象与原对象完全独立,那么浅拷贝就会带来问题。你对拷贝对象深层属性的修改,会不经意间影响到原始对象,这在调试时会让人非常头疼,也可能导致数据不一致。
- 需要深拷贝时的替代方案: 如果你需要一个完全独立的对象副本,你需要实现深拷贝。常见的深拷贝方法包括:
- 使用
JSON.parse(JSON.stringify(obj))
:简单粗暴,但有局限性(无法处理函数、undefined
、Symbol
、循环引用等)。 - 手写递归拷贝函数:最灵活,但实现起来复杂。
- 使用第三方库:如Lodash的
_.cloneDeep()
,这是生产环境中更推荐的做法。
- 使用
所以,在使用Object.assign
时,一定要清楚你的数据结构,特别是是否有嵌套的引用类型属性,并根据实际需求选择合适的拷贝方式。
除了合并对象,Object.assign
还有哪些常见的应用场景?
Object.assign
的用途远不止简单的对象合并,它在日常开发中还有很多巧妙且实用的场景。
1. 对象克隆(浅克隆)
如果你想创建一个现有对象的副本,但又不想直接修改原始对象,Object.assign
是一个快速实现浅克隆的方法。
const originalUser = { name: 'Alice', age: 30 }; const clonedUser = Object.assign({}, originalUser); clonedUser.age = 31; // 修改克隆对象 console.log(originalUser.age); // 30 (原始对象未受影响) console.log(clonedUser.age); // 31
这里我们把一个空对象{}
作为目标对象,这样originalUser
的属性就会被复制到一个全新的对象上,而originalUser
本身保持不变。这在需要基于现有数据创建新数据,同时保持原始数据不变的场景中非常有用,比如在函数式编程中强调数据不可变性。
2. 为对象设置默认值
在处理函数参数或配置对象时,我们经常需要为一些可选属性设置默认值。Object.assign
可以很优雅地实现这一点。
function processConfig(options) { const defaultOptions = { timeout: 5000, retries: 3, debugMode: false }; // 将传入的options合并到defaultOptions上,如果options有同名属性,会覆盖默认值 const finalConfig = Object.assign({}, defaultOptions, options); // 或者直接修改传入的options对象(如果允许) // Object.assign(options, defaultOptions); // 这样会把defaultOptions的属性合并到options上,但如果options有同名属性,options的会保留 // 如果是希望options覆盖defaultOptions,那么顺序是 Object.assign({}, defaultOptions, options) // 如果是希望defaultOptions作为兜底,传入的options优先,那么顺序是 Object.assign({}, options, defaultOptions) // 个人经验,一般是传入的options优先,所以是 Object.assign({}, defaultOptions, options) console.log(finalConfig); } processConfig({ retries: 5, debugMode: true }); // { timeout: 5000, retries: 5, debugMode: true } processConfig({}); // { timeout: 5000, retries: 3, debugMode: false }
通过将默认值对象放在传入的配置对象之前,可以确保传入的配置能够覆盖默认值,而未传入的属性则保留默认值。
3. 模拟混入(Mixin)
在JavaScript中,我们没有像一些面向对象语言那样的传统类继承机制,但可以通过混入(Mixin)模式来复用功能。Object.assign
是实现这种模式的一种方式,可以将一个或多个对象的属性“混入”到另一个对象或类的原型上。
const canWalk = { walk() { console.log('I can walk!'); } }; const canFly = { fly() { console.log('I can fly!'); } }; class Bird { constructor(name) { this.name = name; } } // 将canWalk和canFly的属性混入到Bird的原型上 Object.assign(Bird.prototype, canWalk, canFly); const myBird = new Bird('Sparrow'); myBird.walk(); // I can walk! myBird.fly(); // I can fly!
这种模式允许你将独立的、可复用的行为组合到一起,而不是通过复杂的继承链来管理。
4. 结合展开运算符(Spread Syntax)的替代方案
虽然ES6的展开运算符(...
)在很多情况下是更简洁、更直观的对象合并和克隆方式,但Object.assign
在某些场景下仍然有用,比如:
- 在不支持展开运算符的环境中(需要Babel等工具转换)。
- 当你需要动态地、程序化地决定要合并哪些源对象时,
Object.assign
可以接受一个数组作为sources
参数(虽然需要apply
或reduce
处理一下)。 - 在需要明确返回目标对象而不是新对象的场景(虽然这通常不是首选)。
// 展开运算符的例子 const objA = { x: 1 }; const objB = { y: 2 }; const mergedWithSpread = { ...objA, ...objB }; // { x: 1, y: 2 } // Object.assign 也能做到 const mergedWithAssign = Object.assign({}, objA, objB); // { x: 1, y: 2 }
总的来说,Object.assign
是一个多功能且性能良好的工具,理解其浅拷贝的特性以及这些常见应用场景,能帮助你更有效地组织和操作JavaScript对象。
使用 Object.assign
时需要注意哪些潜在的“坑”或限制?
尽管Object.assign
非常实用,但在使用它时,确实有一些需要注意的细节和潜在的“陷阱”,如果不明就里,可能会导致一些意想不到的行为。
1. 只复制可枚举的自有属性
这是Object.assign
最核心的限制之一。它只会复制源对象中那些“可枚举”的“自有”属性。
- 可枚举(enumerable): 意味着属性的
enumerable
描述符为true
。通过字面量创建的对象属性默认都是可枚举的。但通过Object.defineProperty()
定义的属性,如果enumerable
设置为false
,则不会被复制。 - 自有(own): 意味着属性直接存在于对象本身,而不是在其原型链上。原型链上的属性,即使是可枚举的,也不会被复制。
const proto = { protoProp: 'I am from prototype' }; const source = Object.create(proto); source.ownProp = 'I am own'; Object.defineProperty(source, 'hiddenProp', { value: 'I am not enumerable', enumerable: false }); const target = {}; Object.assign(target, source); console.log(target); // { ownProp: 'I am own' } // protoProp 和 hiddenProp 都没有被复制
这表明Object.assign
在处理继承来的属性或某些特定定义的属性时,并不是一个全能的复制工具。如果你需要复制所有属性(包括不可枚举的或原型链上的),你需要更复杂的遍历和复制逻辑。
2. Getter和Setter的特殊处理
如果源对象中的属性是一个getter或setter,Object.assign
复制的不是getter或setter函数本身,而是它们执行后的值。也就是说,它会调用getter函数,然后将返回的值作为普通的数据属性复制到目标对象上。Setter则不会被触发。
const sourceWithAccessor = { _value: 10, get value() { console.log('Getter called!'); return this._value * 2; }, set value(v) { console.log('Setter called!'); this._value = v; } }; const target = {}; Object.assign(target, sourceWithAccessor); console.log(target); // { _value: 10, value: 20 } // 注意:target.value 是 20,而不是一个getter函数。 // 并且在复制过程中,getter被调用了。 // target现在没有了setter,直接给target.value赋值会覆盖掉它,而不是触发原始的setter逻辑。 target.value = 50; // 这只是一个普通的赋值操作,不会触发原始sourceWithAccessor的setter console.log(target.value); // 50
这个行为在处理包含复杂逻辑的属性时尤其重要,你可能会期望复制的是属性的行为,但实际上复制的只是某个时刻的值。
3. 对原始类型目标的处理
如果Object.assign
的第一个参数(目标对象target
)是原始类型(如null
, undefined
, boolean
, number
, string
, symbol
, bigint
),它会被内部包装成对应的对象(例如,123
会被包装成new Number(123)
)。最终返回的也是这个被包装后的对象。然而,这个行为在实际开发中很少用到,因为你通常会以一个真正的对象作为目标。
const result = Object.assign(123, { a: 1 }); console.log(result); // [Number: 123] console.log(typeof result); // object console.log(result.a); // 1
虽然result
现在是一个Number
对象并且有了a
属性,但原始的数字123
本身并没有被修改。这个特性更多是JavaScript内部类型转换机制的体现,而非Object.assign
的常见用法。
4. null
和undefined
源对象会被跳过
如果源对象是null
或undefined
,Object.assign
会直接跳过它们,不会抛出错误。这通常是一个方便的特性,因为它允许你在不确定源对象是否有效的情况下安全地调用Object.assign
。
const obj = {}; Object.assign(obj, null, { a: 1 }, undefined, { b: 2 }); console.log(obj); // { a: 1, b: 2 }
5. 属性覆盖顺序
当有多个源对象时,属性的复制顺序是从左到右的。如果后面的源对象有与前面源对象同名的属性,后面的属性会覆盖前面的。这通常是预期行为,但在不小心的情况下也可能导致数据丢失或覆盖。
const defaults = { color: 'red', size: 'medium' }; const userPrefs = { size: 'large' }; const finalSettings = Object.assign({}, defaults, userPrefs); console.log(finalSettings); // { color: 'red', size: 'large' } // size 被 userPrefs 覆盖了
理解这些“坑”和限制,能帮助你更准确、更安全地使用Object.assign
,避免在代码中引入难以察觉的bug。在需要深拷贝、处理原型链属性或访问器属性时,你需要考虑其他的解决方案。
以上就是本文的全部内容了,是否有顺利帮助你解决问题?若是能给你带来学习上的帮助,请大家多多支持golang学习网!更多关于文章的相关知识,也可关注golang学习网公众号。
-
501 收藏
-
501 收藏
-
501 收藏
-
501 收藏
-
501 收藏
-
495 收藏
-
287 收藏
-
276 收藏
-
199 收藏
-
392 收藏
-
335 收藏
-
133 收藏
-
385 收藏
-
280 收藏
-
445 收藏
-
265 收藏
-
198 收藏
-
- 前端进阶之JavaScript设计模式
- 设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
- 立即学习 542次学习
-
- GO语言核心编程课程
- 本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
- 立即学习 511次学习
-
- 简单聊聊mysql8与网络通信
- 如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
- 立即学习 498次学习
-
- JavaScript正则表达式基础与实战
- 在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
- 立即学习 487次学习
-
- 从零制作响应式网站—Grid布局
- 本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
- 立即学习 484次学习