-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: implement Object.assgin under the hood
- Loading branch information
1 parent
9c6bc9d
commit b3a1086
Showing
1 changed file
with
242 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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: '[email protected]', | ||
phone: '123-456-7890' | ||
}; | ||
|
||
const userProfile = Object.assign({}, userBasicInfo, userContactInfo); | ||
|
||
console.log(userProfile); | ||
// Output: { name: 'John Doe', age: 30, email: '[email protected]', 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. |