Skip to content

Commit

Permalink
pass prototype through for clean clones, for faster object copying
Browse files Browse the repository at this point in the history
  • Loading branch information
planttheidea committed Sep 26, 2022
1 parent 2b22e42 commit fb52c5a
Show file tree
Hide file tree
Showing 3 changed files with 41 additions and 20 deletions.
42 changes: 31 additions & 11 deletions __tests__/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ describe('getCleanClone', () => {
it('will return a pure object when there is no constructor', () => {
const object = Object.create(null);

const result = utils.getCleanClone(object);
const result = utils.getCleanClone(object, Object.getPrototypeOf(object));

expect(result).not.toBe(object);
expect(result).toEqual(object);
Expand All @@ -70,7 +70,7 @@ describe('getCleanClone', () => {

object.__proto__ = null;

const result = utils.getCleanClone(object);
const result = utils.getCleanClone(object, Object.getPrototypeOf(object));

expect(result).not.toBe(object);
expect(result).toEqual(object);
Expand All @@ -81,22 +81,22 @@ describe('getCleanClone', () => {
it('will return an empty POJO when the object passed is a POJO', () => {
const object = { foo: 'bar' };

const result = utils.getCleanClone(object);
const result = utils.getCleanClone(object, Object.getPrototypeOf(object));

expect(result).not.toBe(object);
expect(result).toEqual({});

expect(Object.getPrototypeOf(result)).toBe(Object.prototype);
});

it('will return an empty object with custom protype when the object created through Object.create()', () => {
it('will return an empty object with custom prototype when the object created through Object.create()', () => {
const object = Object.create({
method() {},
});

object.foo = 'bar';

const result = utils.getCleanClone(object);
const result = utils.getCleanClone(object, Object.getPrototypeOf(object));

expect(result).not.toBe(object);
expect(result).toEqual({});
Expand All @@ -107,7 +107,7 @@ describe('getCleanClone', () => {
it('will return an empty object with the given constructor when it is a global constructor', () => {
const object = new Map();

const result = utils.getCleanClone(object);
const result = utils.getCleanClone(object, Object.getPrototypeOf(object));

expect(result).not.toBe(object);
expect(result).toEqual(new Map());
Expand All @@ -128,7 +128,7 @@ describe('getCleanClone', () => {

const object = new Foo('bar');

const result = utils.getCleanClone(object);
const result = utils.getCleanClone(object, Object.getPrototypeOf(object));

expect(result).not.toBe(object);
expect(result).toEqual(Object.create(Foo.prototype));
Expand All @@ -155,7 +155,12 @@ describe('getObjectCloneLoose', () => {
const handleCopy = jest.fn().mockImplementation((arg) => arg);
const cache = utils.createCache();

const result = utils.getObjectCloneLoose(object, handleCopy, cache);
const result = utils.getObjectCloneLoose(
object,
Object.getPrototypeOf(object),
handleCopy,
cache
);

Object.getOwnPropertySymbols = original;

Expand All @@ -179,7 +184,12 @@ describe('getObjectCloneLoose', () => {
const handleCopy = jest.fn().mockImplementation((arg) => arg);
const cache = utils.createCache();

const result = utils.getObjectCloneLoose(object, handleCopy, cache);
const result = utils.getObjectCloneLoose(
object,
Object.getPrototypeOf(object),
handleCopy,
cache
);

expect(result).not.toBe(object);
expect(result).toEqual(object);
Expand Down Expand Up @@ -215,7 +225,12 @@ describe('getObjectCloneStrict', () => {
const handleCopy = jest.fn().mockImplementation((arg) => arg);
const cache = utils.createCache();

const result = utils.getObjectCloneStrict(object, handleCopy, cache);
const result = utils.getObjectCloneStrict(
object,
Object.getPrototypeOf(object),
handleCopy,
cache
);

Object.getOwnPropertySymbols = original;

Expand Down Expand Up @@ -253,7 +268,12 @@ describe('getObjectCloneStrict', () => {
const handleCopy = jest.fn().mockImplementation((arg) => arg);
const cache = utils.createCache();

const result = utils.getObjectCloneStrict(object, handleCopy, cache);
const result = utils.getObjectCloneStrict(
object,
Object.getPrototypeOf(object),
handleCopy,
cache
);

expect(result).not.toBe(object);
expect(result).toEqual(object);
Expand Down
6 changes: 3 additions & 3 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,12 +54,12 @@ function performCopy<Value>(

// plain objects
if (!Constructor || Constructor === Object) {
return getObjectClone(value, handleCopy, cache);
return getObjectClone(value, prototype, handleCopy, cache);
}

// arrays
if (isArray(value)) {
return getArrayClone(value, handleCopy, cache);
return getArrayClone(value, prototype, handleCopy, cache);
}

const objectClass = toString.call(value);
Expand Down Expand Up @@ -133,7 +133,7 @@ function performCopy<Value>(
}

// assume anything left is a custom constructor
return getObjectClone(value, handleCopy, cache);
return getObjectClone(value, prototype, handleCopy, cache);
}

return handleCopy(value, createCache());
Expand Down
13 changes: 7 additions & 6 deletions src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,10 +55,11 @@ export const createCache =

export function getArrayCloneLoose(
array: any[],
prototype: any,
handleCopy: InternalCopier,
cache: Cache
) {
const clone = new (array.constructor as any)();
const clone = new prototype.constructor();

cache.set(array, clone);

Expand All @@ -72,9 +73,7 @@ export function getArrayCloneLoose(
/**
* Get an empty version of the object with the same prototype it has.
*/
export function getCleanClone(object: any): any {
const prototype = object.__proto__ || getPrototypeOf(object);

export function getCleanClone(object: any, prototype: any): any {
if (!prototype) {
return create(null);
}
Expand All @@ -100,10 +99,11 @@ export function getCleanClone(object: any): any {
*/
export function getObjectCloneLoose(
object: any,
prototype: any,
handleCopy: InternalCopier,
cache: Cache
): any {
const clone: any = getCleanClone(object);
const clone: any = getCleanClone(object, prototype);

// set in the cache immediately to be able to reuse the object recursively
cache.set(object, clone);
Expand Down Expand Up @@ -149,10 +149,11 @@ const getStrictProperties = SYMBOL_PROPERTIES
*/
export function getObjectCloneStrict(
object: any,
prototype: any,
handleCopy: InternalCopier,
cache: Cache
): any {
const clone: any = getCleanClone(object);
const clone: any = getCleanClone(object, prototype);

// set in the cache immediately to be able to reuse the object recursively
cache.set(object, clone);
Expand Down

0 comments on commit fb52c5a

Please sign in to comment.