Skip to content

Commit

Permalink
feat: implement Object.assgin under the hood
Browse files Browse the repository at this point in the history
  • Loading branch information
Shaban-Eissa committed May 31, 2024
1 parent 9c6bc9d commit b3a1086
Showing 1 changed file with 242 additions and 0 deletions.
242 changes: 242 additions & 0 deletions 26.implement-Object-assign.md
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.

0 comments on commit b3a1086

Please sign in to comment.