Skip to content

Latest commit

 

History

History
242 lines (173 loc) · 6.75 KB

26.implement-Object-assign.md

File metadata and controls

242 lines (173 loc) · 6.75 KB

26. implement Object.assign()

Problem

https://bigfrontend.dev/problem/implement-object-assign

Problem Description

The Object.assign() method copies all enumerable own properties from one or more source objects to a target object. It returns the target object. (source: MDN)

It is widely used, Object Spread operator actually is internally the same as Object.assign() (source). Following 2 lines of code are totally the same.

let aClone = { ...a };
let aClone = Object.assign({}, a);

This is an easy one, could you implement Object.assign() with your own implementation?

Solution

/**
 * @param {any} target
 * @param {any[]} sources
 * @return {object}
 */
function objectAssign(target, ...sources) {
  if (!target) {
    throw new Error();
  }

  if (typeof target !== 'object') {
    const constructor = Object.getPrototypeOf(target).constructor;
    target = new constructor(target);
  }

  for (const source of sources) {
    if (!source) {
      continue;
    }

    const keys = [
      ...Object.keys(source),
      ...Object.getOwnPropertySymbols(source),
    ];
    for (const key of keys) {
      const descriptor = Object.getOwnPropertyDescriptor(target, key);
      if (descriptor && !descriptor.configurable) {
        throw new Error();
      }

      target[key] = source[key];
    }
  }

  return target;
}

Usage

const obj = { a: 1 };
const copy = objectAssign({}, obj);

console.log(copy); // { a: 1 }
console.log(copy === obj); // false

console.log(Object.getPrototypeOf(copy) === Object.getPrototypeOf(obj)); // true

console.log(Object.getOwnPropertyDescriptor(copy, "a")); // { value: 1, writable: true, enumerable: true, configurable: true }
console.log(Object.getOwnPropertyDescriptor(obj, "a")); // { value: 1, writable: true, enumerable: true, configurable: true }

console.log(Object.getOwnPropertyDescriptor(copy, "b")); // undefined
console.log(Object.getOwnPropertyDescriptor(obj, "b")); // undefined

Explanation

Let's break down the objectAssign function and its usage:

  1. Function Definition: The objectAssign function is defined to take a target object and any number of sources objects. It's designed to mimic the behavior of the built-in Object.assign method in JavaScript.

  2. Error Checking: If target is not provided or is null, it throws an error. If target is not an object, it creates a new instance of target's prototype's constructor and assigns it to target.

  3. Iterating Over Sources: It then iterates over each source in sources. If a source is not provided or is null, it skips to the next source.

  4. Getting Keys: For each source, it gets an array of its own enumerable property keys and symbols.

  5. Copying Properties: It then iterates over each key in keys. If target has a non-configurable own property with the same key, it throws an error. Otherwise, it assigns the value of the source property to the target property.

  6. Return Value: After all sources have been processed, it returns target.

  7. Usage: The function is then used to create a shallow copy of an object obj. The copy is logged to the console, showing that it has the same properties as obj. It also logs that copy is not the same object as obj, that they have the same prototype, and that they have the same property descriptors for "a" and no property descriptor for "b".

This objectAssign function provides a way to copy properties from one or more source objects to a target object. It throws an error if the target object is not provided or if a non-configurable property of the target object would be overwritten.

Real World Examples

Here are some real-world examples of how Object.assign can be used:

1. Merging Objects

Scenario: Combining user profile information from multiple sources.

const userBasicInfo = {
    name: 'John Doe',
    age: 30
};

const userContactInfo = {
    email: '[email protected]',
    phone: '123-456-7890'
};

const userProfile = Object.assign({}, userBasicInfo, userContactInfo);

console.log(userProfile);
// Output: { name: 'John Doe', age: 30, email: 'john.doe@example.com', phone: '123-456-7890' }

2. Cloning an Object

Scenario: Creating a shallow copy of an object to avoid direct mutation.

const originalObject = {
    name: 'Jane Smith',
    profession: 'Software Developer'
};

const clonedObject = Object.assign({}, originalObject);

clonedObject.profession = 'Senior Software Developer';

console.log(originalObject.profession); // Output: 'Software Developer'
console.log(clonedObject.profession);   // Output: 'Senior Software Developer'

3. Setting Default Values

Scenario: Ensuring an object has default properties if they are missing.

const defaultSettings = {
    theme: 'dark',
    showNotifications: true,
    sounds: true
};

const userSettings = {
    theme: 'light',
    sounds: false
};

const finalSettings = Object.assign({}, defaultSettings, userSettings);

console.log(finalSettings);
// Output: { theme: 'light', showNotifications: true, sounds: false }

4. Deep Merging Nested Objects (with a helper function)

Scenario: Deeply merging nested objects, such as configuration objects.

function deepMerge(target, source) {
    for (const key of Object.keys(source)) {
        if (source[key] instanceof Object && key in target) {
            Object.assign(source[key], deepMerge(target[key], source[key]));
        }
    }
    Object.assign(target || {}, source);
    return target;
}

const config1 = {
    database: {
        host: 'localhost',
        port: 3306
    },
    server: {
        port: 8000
    }
};

const config2 = {
    database: {
        port: 5432,
        username: 'admin'
    },
    server: {
        host: '127.0.0.1'
    }
};

const finalConfig = deepMerge(config1, config2);

console.log(finalConfig);
// Output: 
// {
//   database: { host: 'localhost', port: 5432, username: 'admin' },
//   server: { port: 8000, host: '127.0.0.1' }
// }

5. Adding Methods to an Object

Scenario: Adding methods from a mixin object to an existing object.

const canEat = {
    eat() {
        console.log('Eating...');
    }
};

const canWalk = {
    walk() {
        console.log('Walking...');
    }
};

const person = Object.assign({}, canEat, canWalk);

person.eat(); // Output: 'Eating...'
person.walk(); // Output: 'Walking...'

These examples demonstrate practical uses of Object.assign in various real-world scenarios, highlighting its versatility in handling object manipulation tasks in JavaScript.