Object.assign()
Object.assign(target, ...sources) Object.assign() copies all enumerable own properties from one or more source objects into a target object and returns the modified target. It performs a shallow copy: primitive values are copied by value, but objects and arrays are copied by reference, so nested structures are shared between the original and the copy.
The operation is also used as the classic “merge” pattern: spreading multiple objects into a new empty object produces a merged result without mutating any of the inputs. This was the standard approach before the object spread syntax ({ ...a, ...b }) was introduced in ES2018.
Syntax
Object.assign(target, ...sources)
Parameters
| Parameter | Type | Description |
|---|---|---|
target | object | The target object that receives the copied properties |
sources | object | One or more source objects to copy from |
Return value
The modified target object. Because target is mutated and returned, the return value and the original target are the same reference.
Examples
Basic object merging
const target = { a: 1 };
const source = { b: 2 };
const result = Object.assign(target, source);
console.log(result);
// { a: 1, b: 2 }
console.log(target);
// { a: 1, b: 2 } — target is mutated!
The basic pattern above mutates the target directly, which is fine when you own the object. For cases where you need a fresh combined object without touching the originals, pass an empty object as the first argument and list your sources after it.
Merging multiple sources
const merged = Object.assign(
{ name: "JavaScript" },
{ version: "ES2024" },
{ feature: "top-level await" }
);
console.log(merged);
// { name: "JavaScript", version: "ES2024", feature: "top-level await" }
When multiple sources have the same key, later sources win. Properties are applied left to right, so the last source with a given key determines the final value. This ordering guarantee means you can layer overrides from most generic to most specific.
Shallow cloning an object
const original = { a: 1, b: 2 };
const clone = Object.assign({}, original);
console.log(clone);
// { a: 1, b: 2 }
console.log(clone === original);
// false
Passing an empty object {} as the target is the shallow-clone pattern. The clone is a new object with its own top-level properties, but any nested objects inside it still point to the same references as the original. For deeply nested structures, use structuredClone() instead.
Setting default options
const defaults = { theme: "light", lang: "en", fontSize: 14 };
const userOptions = { theme: "dark" };
const options = Object.assign({}, defaults, userOptions);
console.log(options);
// { theme: "dark", lang: "en", fontSize: 14 }
User options override defaults because userOptions comes after defaults. Neither input object is mutated because the target is a fresh {}. This pattern is widely used for configuration assembly where you need sensible defaults but must respect explicit user choices.
Common patterns
Avoiding mutation — pass {} as the first argument if you don’t want to change the inputs:
const obj = { x: 1 };
// This mutates obj:
Object.assign(obj, { y: 2 });
// This creates a new object and leaves obj alone:
const safe = Object.assign({}, obj, { y: 2 });
The safe pattern creates a new object each time, which is ideal for functional-style code where you want to avoid side effects. When you do want to mutate an existing object — for example, to set up instance properties in a constructor — pass this as the target.
Adding properties to an existing object:
class Config {
constructor(opts) {
Object.assign(this, opts);
}
}
const cfg = new Config({ debug: true, port: 3000 });
console.log(cfg.port); // 3000
Because Object.assign(this, opts) directly sets properties on the instance, it skips setter traps and only copies enumerable own properties from the source. For plain data objects, the non-mutating form Object.assign({}, obj, { y: 2 }) keeps the original intact.
Object.assign() vs spread syntax
Both achieve the same shallow merge, but spread syntax is more concise for most cases:
// Object.assign
const merged = Object.assign({}, a, b);
// Spread syntax (ES2018+)
const merged = { ...a, ...b };
Use Object.assign() when the target must be an existing object (not a new one), or when you need dynamic source lists.
Why Object.assign() still matters
Object.assign() is still useful when you want to mutate a known target object in place or when the list of source objects is built dynamically at runtime. That makes it a good fit for configuration assembly, class constructors, and low-level merge helpers. It is also explicit about its mutation, which can be helpful when the caller should understand that the original target object is being updated.
Shallow copy caveat
The shallow behavior is important to remember: nested arrays and objects are copied by reference, not cloned. That means Object.assign() is safe for top-level property merges, but it is not a deep-clone tool. If the nested structure needs to be independent, use structuredClone() or another deep-copy strategy instead.
See Also
- Object.create() — Create object with specified prototype
- Object.freeze() — Make object immutable
- Object.keys() — Get object property names