
[循环引用]: ./assets/循环引用.svg

目录
=======

<!-- TOC -->

- [1. API简介](#1-api简介)
- [2. 基本使用](#2-基本使用)
- [3. 拷贝函数](#3-拷贝函数)
- [4. 指定拷贝深度](#4-指定拷贝深度)
- [5. 循环引用](#5-循环引用)
- [6. 保持类型信息](#6-保持类型信息)
- [7. 拷贝不可枚举的属性](#7-拷贝不可枚举的属性)
- [8. 自定义拷贝规则](#8-自定义拷贝规则)
    - [8.1. typeCopyers](#81-typecopyers)
        - [8.1.1. Types](#811-types)
        - [8.1.2. 拷贝者Copyer](#812-拷贝者copyer)
    - [8.2. 预设presetTypeCopierMap](#82-预设presettypecopiermap)
    - [8.3. 拷贝者的优先级](#83-拷贝者的优先级)
    - [8.4. getCopy](#84-getcopy)
- [9. 创建带预设拷贝规则的拷贝函数](#9-创建带预设拷贝规则的拷贝函数)

<!-- /TOC -->



内容
========


# 1. API简介
deep-copy 导出了两个工具函数
```
import {deepCopy,createDeepCopy} from "deep-copy"
```

+ `deepCopy<V>(value:V,options?:DeepCopyOptions|null|undefined,typeCopyers?:TypeRevivers<Copier>|null|undefined):V`：对 value 进行深拷贝操作，并将深拷贝后的值返回；参数如下：
   - `value: any`：被拷贝的值；
   - `options?:DeepCopyOptions|null|undefined`：描述拷贝配置的选项对象；
   - `typeCopyers?:TypeRevivers<Copier>|null|undefined`：可根据不同类型定制拷贝逻辑的配置对象

+ `createDeepCopy(presetTypeCopierMap?:TypeReviverMap<Copier>):DeepCopy`：创建并返回带有默认 typeCopyers 的 `deepCopy()` 函数；参数如下：
   - `presetTypeCopierMap?:TypeReviverMap<Copier>`：默认的 typeCopyers；

# 2. 基本使用
deep-copy 库中 会导出 `deepCopy()` 函数，用它可以直接对值进行深拷贝，如下所示：
```
import {deepCopy} from "../dist/deep-copy.es"

let value = {
    name:"root",
    sub:{
        name:"member1",
        fun:function () {
            console.log("这是函数")
        }
    }
};

let copy = deepCopy(value);
```
其中 copy 和 value 的信息结构是完全一样的，但 copy 和 value 及其成员（除了函数（方法）外）已经是完全两份实例，占据着两份不同的内存空间；


# 3. 拷贝函数
默认情况下，`deepCopy()` 不会对 函数对象 进行深拷贝，比如上例中的 `value.sub.fun`，所以，`copy` 中的函数（方法） 和 `value` 中的函数 是同一函数，占据同一份内存空间，即 `copy.sub.fun === value.sub.fun`；

如果希望 `deepCopy()` 对函数也进行深拷贝，可以给 `deepCopy()` 传递第2个参数并设置选项 `copyFun` 为 `true`，如下所示：

```
let copy = deepCopy(value,{
    copyFun:true
});
```
些时，`copy` 中的函数（方法） 和 `value` 中的函数 具有相同的代码逻辑，但已经是两个不同的函数实例，占据不同的内存空间，即 `copy.sub.fun !== value.sub.fun`；



# 4. 指定拷贝深度
如果你只需要实例进行一定深度的深拷贝，比如：只对实例、实例的成员、实例的成员的成员 进行拷贝操作，再深入的不进行拷贝操作，像这样的需求，可以通过给 `deepCopy` 传递 `maxDepth` 选项来实现，如下：
```
let copy = deepCopy(value,{
    maxDepth:2
});
```
`maxDepth` 是可选的，默认值为：`Infinity`；表示拷贝的最大深度；当值为 `undefined` 或 `null` 时，会使用默认值，表示无限深度；被拷贝的值（本例中是 `value`）本身的深度为 0 ，被拷贝值的成员的深度为 1 ，依次类推；


# 5. 循环引用
当对象 与 对象 之间 或 对象 与 成员 之前 存在类似以下引用关系时，就会形成循环引用
![循环引用][]

JSON的深拷贝方案是无法处理循环引用关系 `let copy = JSON.parse(JSON.stringify(value))`，会造成内存溢出；

`deepCopy()` 支持拷贝这种带循环引用关系的对象，并且拷贝后的值，仍然会保持这种循环引用关系，如下所示：
```
let root = {
    name:"root"
};

let member1 = {
    name:"member1",
    fun:function () {
        console.log("这是函数")
    }
};


let member2 = {
    name:"member2",
};

root.sub = member1;
member1.sub = member2;
member2.sub = root;

let rootCopy = DC.deepCopy(root);
```

拷贝前 和 拷贝后的 引用关系保持一样：
```
root.sub.sub.sub === root;  // true
rootCopy.sub.sub.sub === rootCopy;  // true
```


# 6. 保持类型信息
有些时候，被拷贝的对象并不是普通的对象，它可能是某个类的实例，比如： 
```
class Person {
    constructor(){
        this.name = "";
        this.email = "";
    }
}

let p = new Person();
p.name = "郭斌勇";
p.email = "guobinyong@qq.com";
```

`p` 是 类 `Person` 的实例，但通过 JSON方案拷贝后
```
let pCopy = JSON.parse(JSON.stringify(p));

pCopy instanceof Person; // false
pCopy instanceof Object; // true
```
`pCopy` 却变成了普通对象，即 `Object` 类型的实例；

然而，`deepCopy()` 会保持这种类型信息，如下：
```
let pCopy = deepCopy(p);
pCopy instanceof Person;  // true
```
拷贝后的 `pCopy` 仍然是 Person 类型的实例；

**注意：**  
deep-copy 在拷贝实例时，会用实例所属的类创建一个新的实例，创建时并不会给构造函数传递任何参数， 然后将原来实例本身的成员的副本重设 新实例为新实例的成员；这对于那些需要给构造函数传递参数的类 可能会存在问题；如果某类创建实例时依赖构造函数的参数，则您可以针对些类定制拷贝规则；



# 7. 拷贝不可枚举的属性
`deepCopy()` 默认只拷贝对象自身的可枚举属性，如果您也想拷贝对象自身的不可枚举的属性，则可以给 `deepCopy()` 传递 `allOwnProps` 选项，并设置为 `true`，如下：
```
let copy = deepCopy(value,{
    allOwnProps:true
});
```
+ `allOwnProps?:boolean | undefined | null `：可选选项；默认值: `undefined`; 表示是否要拷贝所有自身的属性，包不可枚举的，但不包括原型链上的属性；
   - `true`：拷贝对象自身（不包括原型上的）的所有属性（包括不可枚举的）； 
   - `false|undefined|null` : 只拷贝对象自身中（不包括原型上的）可枚举的属性；

# 8. 自定义拷贝规则

对于下面这个 `Person` 类，在创建实例时（比如：`p`）需要传入一个构造参数 `sex`，这个参数决定了模型的构建过程，如下：
```
class Person {
    constructor(sex){
        this.sex = sex;
        // 根据 sex  初始化模型
        switch(sex){
            case "男":{
                console.log("初始化男性特性");
                break;
            }
            case "女":{
                console.log("初始化女性特性");
                break;
            }
            default:{
                console.log("初始化人妖特性");
            }
        }

        this.name = "";
        this.email = "";
        this.idCard = null; //身份证，是一个对象
        this.mate = null;  //配偶，也是 Person 类的实例
        
    }
}


let p = new Person("男");
p.name = "郭斌勇";
p.email = "guobinyong@qq.com";
p.idCard = {
    id:4231232776886677,  //身份
    address:"中国的一个小农村里"
};

//设置配偶
p.mate = new Person("女");
p.mate.name = "简爱";
```

像这种类型的类，如果使用 `deepCopy()` 的默认拷贝逻辑的话，新拷贝的 `Person` 实例可能会执行错误的构建过程，因为 `deepCopy()` 在创建新实例时，不会传入任何构建参数；像这种类型，我们就需要提供自定义的拷贝规则了；


如果你想自定义某个类型实例的深拷贝规则，有以下几种方案：
- 给 `deepCopy()` 函数提供 `typeCopyers` 参数 或 设置 `presetTypeCopierMap` 预设；
- 给实例增加 `getCopy` 方法；

## 8.1. typeCopyers

可以通过 `typeCopyers` 参数自定义拷贝规则，如下：
```
let pCopy = deepCopy(p,null,{
    // 当需要拷贝 Person 类型的实例时，会回调这个函数，这个函数需要返回 Person 实例的副本；这个函数称为 拷贝者 Copier
    "Person": function(value,copyMember,options){
        let copy = new Person(value.sex);  // 创建新的 Person 实例，作为 value 的副本

        // 给副本 copy 设置基本类型的成员；
        copy.name = value.name;
        copy.email = value.email;

        // 如果引用类型的成员也需要深拷贝，则需要通过 copyMember() 函数拷贝 引用类型的成员
        copy.idCard = copyMember(value.idCard);
        /**
         * copyMember() 函数会返回 拷贝后的值；
         * 但如果被拷贝的值存在循环引用的话，copyMember() 会返回 undefined；这种情况下，可通过给 copyMember() 传递回调函数（第2个参数），通过回调函数来获取拷贝后的值；
         * 无论是否存在循环引用，通过回调函数总能拿到拷贝后的值，回调函数是一种保险的方法
         */
        copyMember(value.mate,function(mateCopy){
            copy.mate = mateCopy;
        });

        // 需要将副本返回；
        return copy;
    }
});
```

`deepCopy()` 函数的 `typeCopyers` 参数是 `TypeRevivers<Copier>` 类型的，是用来描述 类型 和 拷贝者 Copier（提供拷贝实例回调函数）的对应关系的，上例中 `typeCopyers` 使用的是 对象形式，它共以下几种描述方式：
- `{[TypeName:ExactTypeName]:Copyer}`：对象形式，属性名字 是 类型的名字，属性值是类型的 拷贝者（Copyer）；
- `[Types, Copyer][]`：数组形式，数组中的元素是 `[Types, Copyer]` 类型的元组，元组的第一个元素是 `Types`，第二个元素是 拷贝者；
- `Map<Types, Copyer>`：Map形式，Map中的键是 `Types` 类型，值是 拷贝者；


### 8.1.1. Types

`Types` 是 `ExactTypeName`、`ExactType` 和 其数组结构的联合，定义如下：
```
type Types = DataType | DataTypeArray;
type DataType = ExactTypeName | ExactType;
type DataTypeArray = DataType[];
```
- `ExactType`：精确类型，可更加细致地描述类型；各种类型的值（左侧） 与 `ExactType`（右侧） 的映射如下：
   * `undefined` ：`undefined`
   * `null` : `null`
   * `string` : `"string"`
   * `number` : `"number"`
   * `bigint` : `"bigint"`
   * `boolean` : `"boolean"`
   * `symbol` : `"symbol"`
   * 普通函数 : `Function`
   * 异步函数 : `AsyncFunction`
   * 生成器函数 : `GeneratorFunction`
   * 没有原型的对象(如：通过 `Object.create(null)` 创建的对象) : `"object"`
   * 其它任何类型的实例  : 该实例的构造函数

- `ExactTypeName`：精确类型的字符串表示；各种类型的值（左侧） 与 `ExactTypeName`（右侧） 的映射如下：
   * `undefined` ：`"undefined"`
   * `null` : `"null"`
   * `string` : `"string"`
   * `number` : `"number"`
   * `bigint` : `"bigint"`
   * `boolean` : `"boolean"`
   * `symbol` : `"symbol"`
   * 普通函数 : `"Function"`
   * 异步函数 : `"AsyncFunction"`
   * 生成器函数 : `"GeneratorFunction"`
   * 没有原型的对象(如：通过 `Object.create(null)` 创建的对象) : `"object"`
   * 其它任何类型的实例  : 该实例的构造函数的名字

所以，`Types` 可以是具体的类型 `ExactType` ，比如：`Person` 类；也可以是 类型的名字字符串 `ExactTypeName` ，比如：`'Person'`；或者是包含多个类型的数组，比如：`[Person,'Map',Set]`；


### 8.1.2. 拷贝者Copyer
拷贝者 Copyer 是一个类型为 `(value:T,copyMember:CopyMember,options:CopierOptions)=>T` 函数，严格的定义如下：
```
type Copier<T = any,Host = any> = (this:T,value:T,copyMember:CopyMember,options:CopierOptions<Host>)=>T
```

当需要拷贝某个类型的实例时，就会调用这个类型的 拷贝者 Copyer ，然后将拷贝者返回的值作为那个实例的副本；

在调用拷贝者 Copyer 时，会将 Copyer 的 `this` 设置为被拷贝的值（与 `value` 参数相同的值），并会给 Copyer 传递以下参数：
- `value`：被拷贝的值；
- `copyMember`：用于深拷贝 `value` 的成员的深拷贝函数；类型为 `(member:T,completeCB?:CompleteCB<T>|null|undefined,key?:K,host?:H | null | undefined,options?:CopyMemberOptions)=>T|undefined`；拷贝后副本会通过 `copyMember()` 的返回值（当被拷贝的成员不存在循环引用时） 和  `completeCB` 回调函数 返回；
- `options`：包含了其它信息的对象
   ```
    {
       allOwnProps:boolean;
       key:any;
       host:Host;
       type:string;
       depth:number;
       copyFun:boolean;
   }
   ```
拷贝者 Copyer 需要将自定创建的 `value` 的副本返回；

在创建 `value` 的副本的过程中，如果需要对 `value` 的引用类型的成员 进行拷贝，则需要调用 `copyMember(value)` 函数，这个函数是专门用于 深拷贝 `value` 的成员的；拷贝后副本会通过 `copyMember(value)` 的返回值（当被拷贝的成员不存在循环引用时） 和  传给 `copyMember(value,completeCB)` 的 `completeCB` 回调函数 传回；例外的情况是：如果 `value` 的某个成员存在循环引用，则 `copyMember(value)` 会返回 `undefined`，这种情况下，只能通过 `completeCB` 回调函数 传回拷贝后的值；


## 8.2. 预设presetTypeCopierMap
如果经常需要给 `deepCopy()` 传递包含相同 类型和拷贝者 的 `typeCopyers` 参数，则可以将那些经常用到的 拷贝者 设置到 `deepCopy()` 的预设 `deepCopy.presetTypeCopierMap` 中；

`deepCopy` 函数对象有个属性 `presetTypeCopierMap` 是用来设置常用的拷贝者的，它的类型是 `Map<Types, Reviver>`，所以上面的例子也可以如下设置预设：
```
// 当需要拷贝 Person 类型的实例时，会回调些函数
deepCopy.presetTypeCopierMap.set(Person,function(value,copyMember,options){
    let copy = new Person(value.sex);  // 创建新的 Person 实例，作为 value 的副本

    // 给副本 copy 设置基本类型的成员；
    copy.name = value.name;
    copy.email = value.email;

    // 如果引用类型的成员也需要深拷贝，则需要通过 copyMember() 函数拷贝 引用类型的成员
    copy.idCard = copyMember(value.idCard);
    /**
     * copyMember() 函数会返回 拷贝后的值；
     * 但如果被拷贝的值存在循环引用的话，copyMember() 会返回 undefined；这种情况下，可通过给 copyMember() 传递回调函数（第2个参数），通过回调函数来获取拷贝后的值；
     * 无论是否存在循环引用，通过回调函数总能拿到拷贝后的值，回调函数是一种保险的方法
     */
    copyMember(value.mate,function(mateCopy){
        copy.mate = mateCopy;
    });

    // 需要将副本返回；
    return copy;
})
```

以后使用 `deepCopy()` 时，就不需要传递 `Person` 类型的拷贝者了；

## 8.3. 拷贝者的优先级
当需要拷贝某个类型的实例时，`deepCopy()` 会通过以下几种方法获取该实例的副本：
- 首先使用 `typeCopyers` 参数中的拷贝者；
- 如果没有找到拷贝者，则再使用预设 `presetTypeCopierMap` 中的拷贝者；
- 如果没有找到拷贝者，则会将实例的 `getCopy` 方法作为拷贝者；
- 如果实例没有 `getCopy` 方法，则会使用 `typeCopyers` 参数中 `"default"` 对应的 拷贝者；
- 如果没有找到拷贝者，则会使用预设 `presetTypeCopierMap` 中 `"default"` 对应的 拷贝者；
- 如果没有找到拷贝者，则会使用内置的默认拷贝逻辑；

其中，`"default"` 对应的拷贝者称为 默认的拷贝者；

## 8.4. getCopy
通过上文的 [拷贝者的优先级][] 可以知道，我们也可以给对象增加 `getCopy` 方法 来自定义 该对象的拷贝逻辑，如果您想将拷贝者应用到某个类型的所有实例上，则只需要给该类型增加一个实例方法 `getCopy` 即可；

`getCopy` 方法的 和 拷贝者 Copier 的类型一样，都是类型为 `(value:T,copyMember:CopyMember,options:CopierOptions)=>T` 函数；

所以，上例中，也可以如下自定义 实例 `p` 的拷贝逻辑：
```
p.getCopy = function(value,copyMember,options){
    let copy = new Person(value.sex);  // 创建新的 Person 实例，作为 value 的副本

    // 给副本 copy 设置基本类型的成员；
    copy.name = value.name;
    copy.email = value.email;

    // 如果引用类型的成员也需要深拷贝，则需要通过 copyMember() 函数拷贝 引用类型的成员
    copy.idCard = copyMember(value.idCard);
    /**
     * copyMember() 函数会返回 拷贝后的值；
     * 但如果被拷贝的值存在循环引用的话，copyMember() 会返回 undefined；这种情况下，可通过给 copyMember() 传递回调函数（第2个参数），通过回调函数来获取拷贝后的值；
     * 无论是否存在循环引用，通过回调函数总能拿到拷贝后的值，回调函数是一种保险的方法
     */
    copyMember(value.mate,function(mateCopy){
        copy.mate = mateCopy;
    });

    // 需要将副本返回；
    return copy;
};
```

或，如下自定义 `Person` 类型 的拷贝逻辑：
```
Person.prototype.getCopy = function(value,copyMember,options){
    let copy = new Person(value.sex);  // 创建新的 Person 实例，作为 value 的副本

    // 给副本 copy 设置基本类型的成员；
    copy.name = value.name;
    copy.email = value.email;

    // 如果引用类型的成员也需要深拷贝，则需要通过 copyMember() 函数拷贝 引用类型的成员
    copy.idCard = copyMember(value.idCard);
    /**
     * copyMember() 函数会返回 拷贝后的值；
     * 但如果被拷贝的值存在循环引用的话，copyMember() 会返回 undefined；这种情况下，可通过给 copyMember() 传递回调函数（第2个参数），通过回调函数来获取拷贝后的值；
     * 无论是否存在循环引用，通过回调函数总能拿到拷贝后的值，回调函数是一种保险的方法
     */
    copyMember(value.mate,function(mateCopy){
        copy.mate = mateCopy;
    });

    // 需要将副本返回；
    return copy;
};
```


# 9. 创建带预设拷贝规则的拷贝函数
如果您在使用 `deepCopy()` 时，经常需要使用不同的预设，那您可能需要使用 `createDeepCopy(presetTypeCopierMap)` 来创建一个带有预设 `presetTypeCopierMap` 的另一个新的 `deepCopy()` 函数；

`createDeepCopy(presetTypeCopierMap)` 方法会创建一个新的深拷贝函数，这个新的深拷贝函数 和 `deepCopy()` 具有相同的功能，只是 不同的对象，且预设为传给 `createDeepCopy(presetTypeCopierMap)` 函数的 `presetTypeCopierMap` 参数，如下：

```
const presetTypeCopierMap = new Map();
presetTypeCopierMap.set(Person,function(value,copyMember,options){
    let copy = new Person(value.sex);  // 创建新的 Person 实例，作为 value 的副本

    // 给副本 copy 设置基本类型的成员；
    copy.name = value.name;
    copy.email = value.email;

    // 如果引用类型的成员也需要深拷贝，则需要通过 copyMember() 函数拷贝 引用类型的成员
    copy.idCard = copyMember(value.idCard);
    /**
     * copyMember() 函数会返回 拷贝后的值；
     * 但如果被拷贝的值存在循环引用的话，copyMember() 会返回 undefined；这种情况下，可通过给 copyMember() 传递回调函数（第2个参数），通过回调函数来获取拷贝后的值；
     * 无论是否存在循环引用，通过回调函数总能拿到拷贝后的值，回调函数是一种保险的方法
     */
    copyMember(value.mate,function(mateCopy){
        copy.mate = mateCopy;
    });

    // 需要将副本返回；
    return copy;
});

// deepCopy2 为新的深拷贝函数，功能和 deepCopy 一样；
const deepCopy2 = createDeepCopy(presetTypeCopierMap);
```

其中 `deepCopy2` 为新的深拷贝函数，功能和 `deepCopy` 一样；通过 `deepCopy2` 来进行深拷贝操作，如下：
```
let copy = deepCopy2(value);
```