From b3a108668c3e8b1b2790a701313ea29925fc5535 Mon Sep 17 00:00:00 2001 From: Shaban-Eissa Date: Fri, 31 May 2024 12:27:20 +0300 Subject: [PATCH] feat: implement Object.assgin under the hood --- 26.implement-Object-assign.md | 242 ++++++++++++++++++++++++++++++++++ 1 file changed, 242 insertions(+) create mode 100644 26.implement-Object-assign.md diff --git a/26.implement-Object-assign.md b/26.implement-Object-assign.md new file mode 100644 index 0000000..356bb05 --- /dev/null +++ b/26.implement-Object-assign.md @@ -0,0 +1,242 @@ +# 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](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/assign)) + +It is widely used, Object Spread operator actually is internally the same as `Object.assign()` ([source](https://github.com/tc39/proposal-object-rest-spread/blob/master/Spread.md)). Following 2 lines of code are totally the same. + +```js +let aClone = { ...a }; +let aClone = Object.assign({}, a); +``` + +This is an easy one, could you implement `Object.assign()` with your own implementation? + +# + +### Solution + +```js +/** + * @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 +```js +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. + +```javascript +const userBasicInfo = { + name: 'John Doe', + age: 30 +}; + +const userContactInfo = { + email: 'john.doe@example.com', + 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. + +```javascript +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. + +```javascript +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. + +```javascript +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. + +```javascript +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. \ No newline at end of file