@haixing_hu/clone

1.5.0 • Public • Published

@haixing_hu/clone

npm package License 中文文档 CircleCI Coverage Status

clone is a JavaScript library for deeply cloning JavaScript objects. It retains the prototype of the object and all its properties, and supports custom clone hook functions, allowing for specialized clone algorithms for specific types.

This library has the following features:

  • Deep Cloning: Capable of deeply cloning any JavaScript object, including but not limited to simple objects, instances of custom classes, Array, Map, Set, Date, RegExp, Error, Promise, etc.
  • Prototype Retention: The cloned object maintains the prototype of the original object.
  • Cycle Reference Detection: Capable of detecting cyclic references and preventing infinite recursion.
  • Support for Custom Attributes on Built-in Objects: Given JavaScript's flexibility, users can set custom attributes on built-in objects, e.g., const str = 'hello'; str.x = 123;. This library can clone these custom attributes.
  • Customizable Cloning Parameters: Supports custom cloning parameters, allowing for customization of the cloning algorithm.
  • Customizable Cloning Algorithm: Supports the customization of the cloning algorithm through the registration of hook functions.
  • Vue.js Reactivity Support: Compatible with the reactivity system of Vue.js, cloning only enumerable properties.

Table of Contents

Installation

This library depends on the typeinfo library, so it's necessary to install typeinfo first.

Install via npm:

npm install @haixing_hu/typeinfo @haixing_hu/clone

Or install via yarn:

yarn add @haixing_hu/typeinfo @haixing_hu/clone

Usage

class Credential {
  type = '';
  number = '';
}
class Person {
  name = '';
  age = 0;
  credential = new Credential();
}
const obj2 = new Person();
obj2.name = 'Bill Gates';
obj2.age = 30;
obj2.credential.type = 'PASSWORD';
obj2.credential.number = '111111';
const copy2 = clone(obj2);
expect(copy2).toEqual(obj2);
expect(copy2).not.toBe(obj2);
expect(copy2).toBeInstanceOf(Person);
expect(copy2.credential).toBeInstanceOf(Credential);

API Documentation

clone(source, [options])

Deep clones a value or object.

  • source: any - The value or object to be cloned.
  • options: object - Options for the cloning algorithm. Possible options include:
    • includeAccessor: boolean: If true, clones the property's accessors (getters and setters). Defaults to false.
    • excludeReadonly: boolean: If true, does not clone readonly properties. Defaults to false.
    • includeNonEnumerable: boolean: If true, clones non-enumerable properties. Defaults to false.
    • includeNonConfigurable: boolean: If true, clones non-configurable properties. Defaults to false.

The clone function supports cloning customized objects as well as JavaScript built-in values and objects, including but not limited to primitive types, arrays, Map, Set, etc. The specific support is as follows:

  • Primitive types undefined, null, boolean, number, string, symbol, bigint: Returns the original value directly.
  • Function type: Fully implementing clone for functions brings many technical troubles, so this function does not clone function types and returns the original function directly.
  • Object types: Divided into JavaScript built-in objects and user objects:
    • Ordinary non-container built-in objects: Returns a new object identical to the original, including custom attributes added by the user on the original object, which will also be deeply cloned.
    • Built-in container objects, including Array, Map, Set, Int8Array, BigUint64Array, etc.: Clones the container object itself and deeply clones the elements within the container object.
    • Weak reference objects, including WeakMap, WeakSet, WeakRef, etc.: Cannot be cloned, returns the object itself.
    • Buffer objects, including ArrayBuffer, SharedArrayBuffer, etc.: Clones the container object itself and clones the data within the container object.
    • Promise objects: Clones a new Promise object, including custom attributes added by the user on the original object.
    • Intl built-in object's sub-objects, including Intl.Collator, Intl.DateTimeFormat, etc.: Cannot be cloned, returns the object itself.
    • Iterator objects, including ArrayIterator, MapIterator, SetIterator, etc.: Cannot be cloned, returns the object itself.
    • arguments object representing function parameters: Cannot be cloned, returns the object itself.
    • FinalizationRegistry object: Cannot be cloned, returns the object itself.
    • Generator objects, including Generator, AsyncGenerator: Cannot be cloned, thus returns the object itself.
    • Global object: Cannot be cloned, returns the object itself.
    • Other user-defined objects: Deeply clones all properties of the object and maintains the prototype of the cloned object. Whether to clone readonly, non-enumerable, non-configurable, accessor properties, etc., depends on the second argument provided to the clone() function.

registerCloneHook(hook)

Registers a custom object cloning hook function.

  • hook: function - The hook function, which should be in the form of:
    function cloneHook(info, obj, options) {};
    Where:
    • info: object: Type information of the object to be cloned, provided by typeInfo() function.
    • obj: object: The object to be cloned, guaranteed non-null.
    • options: object: Options for the cloning algorithm.

unregisterCloneHook(hook)

Unregisters a custom object cloning hook function.

  • hook: function - The hook function to unregister, in the same form and parameters as registerCloneHook().

cloneImpl(source, options, cache)

Implements the specific clone algorithm. This is an internal used function that can be used to implement custom clone hook functions.

  • source: any - The object to be cloned.
  • options: object - Options for the cloning algorithm.
  • cache: WeakMap - Object cache used to prevent circular references.

copyProperties(source, target, options, cache)

Copies properties from the source object to the target object. This is an internal used function that can be used to implement custom clone hook functions.

  • source: any - The source object.
  • target: any - The target object.
  • options: object - Options for the cloning algorithm.
  • cache: WeakMap - Object cache used to prevent circular references.

Examples

Deep Cloning Objects

The following code example demonstrates how to deeply clone an object, which can be a simple object or an instance of a custom class.

import clone from '@haixing_hu/clone';

const obj1 = { a: 1, b: { c: 2 } };
const copy1 = clone(obj1);
expect(copy1).toEqual(obj1);
expect(copy1).not.toBe(obj1);

class Credential {
  type = '';
  number = '';
}
class Person {
  name = '';
  age = 0;
  credential = new Credential();
}
const obj2 = new Person();
obj2.name = 'Bill Gates';
obj2.age = 30;
obj2.credential.type = 'PASSWORD';
obj2.credential.number = '111111';
const copy2 = clone(obj2);
expect(copy2).toEqual(obj2);
expect(copy2).not.toBe(obj2);
expect(copy2).toBeInstanceOf(Person);
expect(copy2.credential).toBeInstanceOf(Credential);

Cloning with Options

The following code example demonstrates how to use custom cloning algorithm options. For specific options, refer to the API Documentation.

const obj = {
  x: 1,
  y: 2,
  _name: 'obj',
  get z() {
    return this.x + this.y;
  },
  get name() {
    return this._name;
  },
  set name(s) {
    this._name = s;
  },
};
Object.defineProperties(obj, {
  r: {
    value: 'readonly',
    writable: false,
    configurable: true,
    enumerable: true,
  },
});
Object.defineProperties(obj, {
  nc: {
    value: 'non-configurable',
    writable: true,
    configurable: false,
    enumerable: true,
  },
});
Object.defineProperties(obj, {
  ne: {
    value: 'non-enumerable',
    writable: true,
    configurable: true,
    enumerable: false,
  },
});

// clone with default options
const copy1 = clone(obj);
expect(copy1.x).toBe(1);
expect(copy1.y).toBe(2);
expect(copy1.r).toBe('readonly');
expect(copy1.z).toBe(3);
expect(typeof copy1.z).toBe('number');
expect(copy1.name).toBe('obj');
expect(typeof copy1.name).toBe('string');
expect(copy1._name).toBe('obj');
expect(typeof copy1._name).toBe('string');
expect('nc' in copy1).toBe(false);
expect('ne' in copy1).toBe(false);

// clone with customized options
const options = {
  includeAccessor: true,
  excludeReadonly: true,
  includeNonEnumerable: true,
  includeNonConfigurable: false,
};
const copy2 = clone(obj, options);
expect(copy2.x).toBe(1);
expect(copy2.y).toBe(2);
expect('r' in copy2).toBe(false);
expect(copy2.z).toBe(3);
expect(copy2._name).toBe('obj');
expect(copy2.name).toBe('obj');
const zd = Object.getOwnPropertyDescriptor(copy2, 'z');
expect(typeof zd.get).toBe('function');
expect(typeof zd.set).toBe('undefined');
expect('value' in zd).toBe(false);
const nd = Object.getOwnPropertyDescriptor(copy2, 'name');
expect(typeof nd.get).toBe('function');
expect(typeof nd.set).toBe('function');
expect('value' in nd).toBe(false);
copy2.name = 'xxx';
expect(copy2.name).toBe('xxx');
expect(copy2._name).toBe('xxx');
expect('ne' in copy2).toBe(true);
expect(copy2.ne).toBe('non-enumerable');
expect('nc' in copy2).toBe(false);

Customizing Clone Behavior

import { registerCloneHook, clone } from '@haixing_hu/clone';

function customCloneHook(info, obj, options) {
  if (info.constructor === MyCustomClass) {
    const result = new MyCustomClass();
    // Implements the customized clone algorithm
    return result;
  }
  return null;
}

registerCloneHook(customCloneHook);

const original = {
  name: 'original',
  data: new MyCustomClass(),
};
const cloned = clone(original);

unregisterCloneHook(customCloneHook);

License

clone is distributed under the Apache 2.0 License. For more details, please refer to the LICENSE file.

Contributing

If you encounter any issues or have suggestions for improvements, feel free to open an issue or submit a pull request in the GitHub repository.

Contributors

Readme

Keywords

none

Package Sidebar

Install

npm i @haixing_hu/clone

Weekly Downloads

108

Version

1.5.0

License

Apache-2.0

Unpacked Size

3.16 MB

Total Files

91

Last publish

Collaborators

  • haixing-hu