npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

@haixing_hu/clone

v1.12.0

Published

A JavaScript library provides a deep clone function

Downloads

538

Readme

@haixing_hu/clone

npm package License English Document CircleCI Coverage Status

clone 是一个 JavaScript 库,用于深度克隆 JavaScript 对象。它保持对象及其所有属性的原型, 并支持自定义克隆钩子函数,允许对指定类型执行特殊的克隆算法。

不同于内置的 structuredClone() 函数,此开源库具有以下特性:

  • 深度克隆:能够深度克隆任意 JavaScript 对象,包括但不限于简单对象、自定义类的实例、 ArrayMapSetDateRegExpErrorPromise 等。
  • 保持原型:克隆的对象保持原有对象的原型。
  • 循环引用检测:能够检测循环引用,并防止无限递归。
  • 支持内置对象的自定义属性:由于 JavaScript 语言的灵活性,对于内置对象,用户可以在该对象 上任意设置自定义属性,例如const str = 'hello'; str.x = 123;,本函数库能够克隆这些自定 义属性。
  • 可自定义的克隆算法参数:支持自定义克隆算法参数,通过参数定制克隆算法。
  • 可自定义的命名转换规则:支持自定义命名转换规则,允许转换克隆结果对象属性的命名风格。
  • 可自定义的克隆算法:支持自定义克隆算法,通过注册钩子函数定制对特定类型的克隆算法。
  • Vue.js 反应性支持:兼容 Vue.js 的反应性系统,只克隆可枚举属性。

目录

安装

此函数库依赖naming-styletype-detecttypeinfo函数库,因此需要先安装它们。

通过 npm 安装:

npm install @babel/runtime @haixing_hu/naming-style @haixing_hu/type-detect @haixing_hu/typeinfo @haixing_hu/clone

或者通过 yarn 安装

yarn add @babel/runtime @haixing_hu/naming-style @haixing_hu/type-detect @haixing_hu/typeinfo @haixing_hu/clone

使用方法

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 文档

clone(source, [options])

深度克隆一个值或对象。

  • source: any - 要克隆的值或对象。
  • options: object - 克隆算法的选项对象。可能的选项包括:
    • includeAccessor: boolean:若为 true,将克隆属性的访问器(即 getter 和 setter)。 默认为 false
    • excludeReadonly: boolean:若为 true,将不克隆只读属性。默认为 false
    • includeNonEnumerable: boolean:若为 true,将克隆非枚举属性。默认为 false
    • includeNonConfigurable: boolean:若为 true,将克隆非可配置属性。默认为 false
    • convertNaming: boolean - 若为 true,克隆算法将根据指定的命名风格转换目标对象的属性名称。 此选项的默认值为 false
    • sourceNamingStyle: string | NamingStyle, 源对象的命名样式。该选项仅在 convertNaming 选项设置为 true 时有效。该选项的值可以是表示命名样式名称的字符串,也可以是一个 NamingStyle 实例。默认值为 LOWER_CAMEL
    • targetNamingStyle: string | NamingStyle, 目标对象(即克隆结果对象)的命名样式。 该选项仅在 convertNaming 选项设置为 true 时有效。该选项的值可以是表示命名样式名称 的字符串,也可以是一个NamingStyle 实例。默认值为 LOWER_CAMEL
    • pojo: boolean - 如果此选项设置为 true,克隆算法将把源对象转换为普通的 JavaScript 对象 (POJO)。此选项的默认值为false
    • removeEmptyFields: boolean - 如果此选项设置为 true,克隆算法将在克隆之前递归地移 除源对象的空字段。所谓空字段,是指值为nullundefined,空字符串,空数组,空集合的字段。 此选项的默认值为false
    • disableHooks: boolean - 如果此选项设置为 true,克隆算法将禁用克隆钩子函数。 此选项的默认值为 false
    • useToJSON: boolean - 如果该选项设置为 true,并且源对象具有 toJSON() 方法, 克隆算法将使用源对象的 toJSON() 方法作为克隆的结果。此选项的默认值为 false
    • skipRootToJSON: boolean - 如果该选项和 useToJSON 选项都设置为 true,并且源 对象具有 toJSON() 方法,克隆算法只有在源对象不是克隆过程的根对象时,才会使用 toJSON() 方法的结果作为克隆结果。当使用 clone() 函数实现类或对象的 toJSON() 方法时,该选项 非常有用,因为它可以避免无限递归。此选项的默认值为 false

克隆函数支持对 JavaScript 内置对象的克隆,包括但不限于 primitive 类型、数组、MapSet等。 具体的支持如下:

  • primitive 类型 undefinednullbooleannumberstringsymbolbigint:直接返回原始值;
  • 函数类型:要完整实现对函数的 clone 会带来很多技术上的麻烦,因此本函数对函数类型的值不做克隆,直接返回原始函数;
  • 对象类型:分为 JavaScript 内置对象和用户对象两种情况:
    • 普通非容器型内置对象:会返回一个新的对象,和原始对象完全一致,包括用户增加在原始对象上的自定义属性, 也会一起被深度克隆;
    • 内置容器对象,包括ArrayMapSetInt8ArrayBigUint64Array等:会克隆容器对象 本身,同时深度克隆容器对象中的元素;
    • 弱引用对象,包括:WeakMapWeakSetWeakRef等:不可被克隆,直接返回该对象本身;
    • Buffer对象,包括 ArrayBufferSharedArrayBuffer等:会克隆容器对象本身,同时克隆容器对象中的数据;
    • Promise对象:会克隆一个新的 Promise 对象,包括用户增加在原始对象上的自定义属性;
    • Intl 内置对象的子对象,包括Intl.CollatorIntl.DateTimeFormat等:不可被克隆,直接返回该对象本身;
    • Iterator 对象,包括ArrayIteratorMapIteratorSetIterator等:不可被克隆,直接返回该对象本身;
    • 表示函数参数的 arguments 对象:不可被克隆,直接返回该对象本身;
    • FinalizationRegistry 对象:不可被克隆,直接返回该对象本身;
    • 生成器对象,包括 GeneratorAsyncGenerator:不可被克隆,因此直接返回该对象本身;
    • 全局对象:不可被克隆,直接返回该对象本身;
    • 其他用户自定义对象:深度克隆该对象所有属性,并保持被克隆对象的原型。是否克隆只读属性、不可枚举属性、不可配置属性、 访问器属性等,取决于调用 clone() 函数的第二个克隆算法选项参数。

registerCloneHook(hook)

注册一个自定义对象克隆的钩子函数。

  • hook: function - 钩子函数,其形式应为:
    function cloneHook(info, obj, options) {};
    其中:
    • info: object:待克隆对象的类型信息,由 typeInfo() 函数提供。
    • obj: object:待克隆的对象,保证非空。
    • options: object:克隆算法的选项。

unregisterCloneHook(hook)

注销一个自定义对象克隆的钩子函数。

  • hook: function - 要注销的钩子函数,其形式和参数与 registerCloneHook() 相同。

cloneImpl(source, depth, options, cache)

实现了具体的clone 算法。这是一个内部函数,可用于实现自定义的克隆钩子函数。

  • source: any - 待克隆的对象。
  • depth: number - 当前克隆的深度。根对象的深度为 0。
  • options: object - 克隆算法的选项。
  • cache: WeakMap - 用于防止循环引用的对象缓存。

copyProperties(source, target, depth, options, cache)

将源对象的属性复制到目标对象。这是一个内部函数,可用于实现自定义的克隆钩子函数。

  • source: any - 源对象。
  • target: any - 目标对象。
  • depth: number - 当前克隆的深度。根对象的深度为 0。
  • options: object - 克隆算法的选项。
  • cache: WeakMap - 用于防止循环引用的对象缓存。

示例

深度克隆对象

下面的代码例子展示了如何深度克隆一个对象,可以是简单对象,也可以是自定义类的实例。

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);

克隆算法选项

下面的代码例子展示了如何使用自定义的克隆算法选项。具体的选项请参考API 文档

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);

带命名转换的克隆

以下代码示例演示了如何使用自定义命名转换规则进行克隆。具体选项请参考 API 文档

import clone from '@haixing_hu/clone';

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

const obj = {
  first_field: 'first-field',
  second_field: {
    first_child_field: 'first-child-field',
    second_child_field: {
      the_person: person,
    },
  }
};
const copy = clone(obj, {
  convertNaming: true,
  sourceNamingStyle: 'lower-underscore',
  targetNamingStyle: 'lower_camel',
});
expect(copy).toBeInstanceOf(Object);
expect(copy.firstField).toBe(obj.first_field);
expect(copy.secondField).toBeInstanceOf(Object);
expect(copy.secondField.firstChildField).toBe(obj.second_field.first_child_field);
expect(copy.secondField.secondChildField).toBeInstanceOf(Object);
expect(copy.secondField.secondChildField.thePerson).toBeInstanceOf(Person);
expect(copy.secondField.secondChildField.thePerson).toEqual(person);
expect(copy.secondField.secondChildField.thePerson).not.toBe(person);

请注意,命名转换样式可以通过字符串或 NamingStyle 实例指定。如果通过字符串指定,则字符串不 区分大小写,并且字符 '-''_'; 被视为相同。有关更多详细信息,请参阅 NamingStyle.of() 函数。

定制克隆行为

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);

许可证

clone 在 Apache 2.0 许可下分发。有关更多详情,请参阅 LICENSE 文件。

贡献方式

如果您发现任何问题或有改进建议,请随时在[GitHub仓库]中提出问题或提交拉取请求。

贡献者