Implementing Custom Object.assign in JavaScript
Implementing a custom version of Object.assign
is a great exercise to understand how object property assignment works in JavaScript. The Object.assign
method is used to copy the values of all enumerable own properties from one or more source objects to a target object.
What is Object.assign
?
Object.assign
is a method used to copy the values of all enumerable own properties from one or more source objects to a target object. It returns the target object.
Real Interview Insights
Interviewers might ask you to:
- Implement a custom
Object.assign
function. - Explain the difference between shallow and deep copying.
- Handle edge cases such as non-object targets or sources.
Implementing Custom Object.assign
Here’s how you can implement a custom version of Object.assign
:
function customAssign(target, ...sources) {
if (target == null) {
throw new TypeError('Cannot convert undefined or null to object');
}
const to = Object(target);
sources.forEach(source => {
if (source != null) {
for (let key in source) {
if (Object.prototype.hasOwnProperty.call(source, key)) {
to[key] = source[key];
}
}
// Copying symbols (if the environment supports them)
if (typeof Object.getOwnPropertySymbols === 'function') {
const symbols = Object.getOwnPropertySymbols(source);
symbols.forEach(symbol => {
if (Object.prototype.propertyIsEnumerable.call(source, symbol)) {
to[symbol] = source[symbol];
}
});
}
}
});
return to;
}
Explanation:
- Parameter Check: Ensure the target is not
null
orundefined
, throwing aTypeError
if it is. - Convert to Object: Convert the target to an object if it’s not already one.
- Iterate Over Sources: Loop through each source object and copy its own properties to the target object.
- Copy Symbols: If symbols are supported, copy enumerable symbol properties as well.
Practical Example
Consider an example with multiple source objects:
const target = { a: 1 };
const source1 = { b: 2 };
const source2 = { c: 3, a: 4 };
const result = customAssign(target, source1, source2);
console.log(result); // Output: { a: 4, b: 2, c: 3 }
console.log(target); // Output: { a: 4, b: 2, c: 3 }
In this example:
- Overwriting Properties: Properties in later source objects overwrite those in earlier ones.
- Mutating the Target: The
customAssign
function mutates the target object and returns it.
Handling Edge Cases
- Primitive Targets: Convert non-object targets (like strings or numbers) to objects.
- Non-Enumerable Properties: Only copy enumerable properties.
- Symbol Properties: Handle copying of symbol properties if supported by the environment.
Enhanced Implementation with Edge Case Handling
function customAssign(target, ...sources) {
if (target == null) {
throw new TypeError('Cannot convert undefined or null to object');
}
const to = Object(target);
sources.forEach(source => {
if (source != null) {
for (let key in source) {
if (Object.prototype.hasOwnProperty.call(source, key)) {
to[key] = source[key];
}
}
// Copying symbols (if the environment supports them)
if (typeof Object.getOwnPropertySymbols === 'function') {
const symbols = Object.getOwnPropertySymbols(source);
symbols.forEach(symbol => {
if (Object.prototype.propertyIsEnumerable.call(source, symbol)) {
to[symbol] = source[symbol];
}
});
}
}
});
return to;
}
// Test cases
console.log(customAssign({ a: 1 }, { b: 2 }, { c: 3, a: 4 })); // { a: 4, b: 2, c: 3 }
console.log(customAssign({}, { a: 1 }, null, { b: 2 })); // { a: 1, b: 2 }
console.log(customAssign('string', { a: 1 })); // TypeError: Cannot convert undefined or null to object
Use Cases for Custom Object.assign
- Merging Objects: Combining multiple objects into one.
- Cloning Objects: Creating a shallow copy of an object.
- Default Parameters: Setting default values for function parameters.
Coding Challenge: Adding a Deep Copy Option
Challenge: Enhance the customAssign
function to include an option for deep copying.
function customAssign(target, ...sources) {
if (target == null) {
throw new TypeError('Cannot convert undefined or null to object');
}
const to = Object(target);
sources.forEach(source => {
if (source != null) {
for (let key in source) {
if (Object.prototype.hasOwnProperty.call(source, key)) {
if (typeof source[key] === 'object' && source[key] !== null) {
to[key] = customAssign({}, source[key]); // Deep copy
} else {
to[key] = source[key];
}
}
}
// Copying symbols (if the environment supports them)
if (typeof Object.getOwnPropertySymbols === 'function') {
const symbols = Object.getOwnPropertySymbols(source);
symbols.forEach(symbol => {
if (Object.prototype.propertyIsEnumerable.call(source, symbol)) {
to[symbol] = source[symbol];
}
});
}
}
});
return to;
}
// Example usage with deep copy
const target = { a: { x: 1 } };
const source = { a: { y: 2 }, b: 3 };
const result = customAssign(target, source);
console.log(result); // Output: { a: { x: 1, y: 2 }, b: 3 }
console.log(target); // Output: { a: { x: 1, y: 2 }, b: 3 }
In this challenge:
- Deep Copy Option: Recursively copy nested objects for a deep copy.