Skip to content

Commit

Permalink
feat: use a WeakMap to track objects and detect circular references…
Browse files Browse the repository at this point in the history
… for `_deepClone(source)`
  • Loading branch information
cheton committed Nov 18, 2024
1 parent 2c81177 commit 0642b3d
Showing 1 changed file with 24 additions and 12 deletions.
36 changes: 24 additions & 12 deletions packages/utils/src/shared.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,17 +15,29 @@ const _joinWords = (words) => {
return `'${words.slice(0, -1).join('\', \'')}', and '${words.slice(-1)}'`;
};

const _deepClone = (source) => {
const _deepClone = (source, seen = new WeakMap()) => {
// Use a `WeakMap` to track objects and detect circular references.
// If the object has been cloned before, return the cached cloned version.
if (seen.has(source)) {
return seen.get(source);

Check warning on line 22 in packages/utils/src/shared.js

View check run for this annotation

Codecov / codecov/patch

packages/utils/src/shared.js#L22

Added line #L22 was not covered by tests
}

if (Array.isArray(source)) {
return source.map((item) => _deepClone(item));
const clonedArray = [];
seen.set(source, clonedArray);
for (let i = 0; i < source.length; ++i) {
clonedArray[i] = _deepClone(source[i], seen);

Check warning on line 29 in packages/utils/src/shared.js

View check run for this annotation

Codecov / codecov/patch

packages/utils/src/shared.js#L26-L29

Added lines #L26 - L29 were not covered by tests
}
return clonedArray;

Check warning on line 31 in packages/utils/src/shared.js

View check run for this annotation

Codecov / codecov/patch

packages/utils/src/shared.js#L31

Added line #L31 was not covered by tests
}

if (isPlainObject(source)) {
const output = {};
Object.keys(source).forEach((key) => {
output[key] = _deepClone(source[key]);
});
return output;
const clonedObject = {};
seen.set(source, clonedObject);
for (const [key, value] of Object.entries(source)) {
clonedObject[key] = _deepClone(value, seen);
}
return clonedObject;
}

// For primitive values and other types, return as is
Expand Down Expand Up @@ -74,13 +86,13 @@ export const merge = (target, source, options = { clone: true }) => {
// Merge plain objects
if (isPlainObject(target) && isPlainObject(source)) {
const output = options.clone ? { ...target } : target;
Object.keys(source).forEach((key) => {
if (isPlainObject(source[key]) && isPlainObject(output[key]) && Object.prototype.hasOwnProperty.call(output, key)) {
output[key] = merge(output[key], source[key], options);
for (const [key, value] of Object.entries(source)) {
if (isPlainObject(value) && Object.prototype.hasOwnProperty.call(output, key) && isPlainObject(output[key])) {
output[key] = merge(output[key], value, options);
} else {
output[key] = options.clone ? _deepClone(source[key]) : source[key];
output[key] = options.clone ? _deepClone(value) : value;
}
});
}
return output;
}

Expand Down

0 comments on commit 0642b3d

Please sign in to comment.