那些你不知道的深度复制

354 阅读2分钟

序言

本篇文章主要是为了探索下是如何进行深度的复制的,其实小编也看过很多大佬写的deepcopy了,但是其实自己也想写一个毕竟用自己的插件还是最爽的,好了,话不多说开始吧。。。

前奏

开始之前先给大家介绍一款插件where-type,这个插件具体干什么的呢,大家可以看详细的README,其实就是以各种方式获取数据类型的,个人感觉不错。也希望大家多多关注下

直接上代码

1. 如何判断基本数据类型

// --------------- 定义部分 ----------------------
const { getTypes } = require('where-type')
// 这些对象值得深度克隆
const OBJECT_TYPES = [
  {},
  [],
  new Map(),
  new Set(),
  new Error(),
  new Date(),
  /^$/
].map((item) => getTypes(item))
// ------------------- 代码判断部分 ----------------
// 除了上述的类型 都是按基本数据类型计算
const type = getTypes(target)
if (!OBJECT_TYPES.includes(type)) return target
  • 通过上述的变量OBJECT_TYPES,我们可以判断如果遇到这些类型以外的类型,我们就认为是基本数据类型
  • 但是,也许很多人会有疑问,比如函数呢,Symbol类型呢,我们都认为是基本数据类型吗
  • 其实我个人认为函数是没有必须复制的,使用原来的函数就行。原因有二:
    • 其一是函数一般都是直接调用了,要么就是进行逻辑处理,要么就是需要返回值
    • 其二是函数的比较实在是没有意义,最起码到现在为止小编是没有见过函数用来进行比较的
    • 如果有的小伙伴就是非常的固执,就是想进行比较那么可以参照下面的写法
if (isFunction(target)) return (...args) => target.call(this, ...args)
  • 关于Symbol类型该做如何处理,我们会在后续慢慢讲解
  • 那么接下来就是判断上面被过滤的类型

2. 判断Error 以及Date类型

// ------------ 定义类型的部分 --------------------
const { getTypes } = require('where-type')
const CONSTRUCT_TYPE = [new Error(), new Date()].map((item) => getTypes(item))
// --------------- 代码执行部分 ------------------
// 针对error,date做处理
if (CONSTRUCT_TYPE.includes(type)) return new target.constructor(target)
  • 针对Error,Date类型判断,首先这些类型可以通过new来调用的,最起码说明我们可以获取到Constructor。既然能拿到Constructor,那我们自己就可以来new一下子
  • 可能有一部分人不了解Constructor怎么来的,接下来我们解析下:
    • 首先我们拿到的一定是实例,就拿Date类型来说的,例如实例是date。
    • date的原型链指向谁呢,date.__proto__ === Date.prototype, 通过左侧的等式我们就可以找到原型,其实原型上就有属性Consructor
    • 图片.png
  • 通过上述的推断,我们直接new target.Constructor(target) 不就是一个新实例吗

3. 判断Symbol类型

// ----------------- 定义部分 ---------------------
const { getTypes } = require('where-type')
const SYMBOL_TYPE = getTypes(Symbol('1'))
// ----------------- 代码执行部分 -----------------
if (SYMBOL_TYPE === type) return Object(Symbol.prototype.valueOf.call(target))
  • 我们通过Symbol.prototype.valueOf来获取Symbol的值
  • 我们可以通过Object来获取新的Symbol()值

4. 判断正则表达式

// ----------------- 定义部分 -----------------------
const { getTypes } = require('where-type')
const REGEXP_TYPE = getTypes(/^$/)

// ---------------- 代码执行部分 --------------------
// 针对正则表达式
if (REGEXP_TYPE === type) {
  const flags = /\w*$/
  const result = new target.constructor(target.source, flags.exec(target))
  result.lastIndex = target.lastIndex
  return result
}
  • 其实正则表达式的判断跟之前的很类似,但是值得我们注意的是一些细节:
    • 其实上述的处理添加了一个备选正则,例如:正则表达式中第二个参数,表示如果传递的参数无效的话,将从备选flags中进行匹配
    • 同时需要归置lastIndex,可能有的人会有疑惑,为什么需要这么做的??是因为可能你现在深度复制的正则,之前已经被匹配过了,lastIndex已经发生了变化。所以我们需要将lastIndex进行归置

5. 针对Set,Map处理

// ----------------- 代码定义的部分 --------------------
const { getTypes } = require('where-type')
const MAP_TYPE = getTypes(new Map())
const SET_TYPE = getTypes(new Set())
// ---------------- 代码执行的部分 --------------------
// 针对set处理
if (SET_TYPE === type) {
  target.forEach((value) => {
    copyTarget.add(deepCopy(value, weakMap))
  })
  return copyTarget
}

// 针对map处理
if (MAP_TYPE === type) {
  target.forEach((value, key) => {
    copyTarget.set(key, deepCopy(value, weakMap))
  })
  return copyTarget
}
  • 其实Set,Map处理就简单很多了,通过上述的代码就可以看到,无非是创建一个新的构造函数来进行添加等

结尾

以上就是我们通过深度复制的原理,以及分析。如果有不同意见的小伙伴可以直接提出来。大家一起学习学习。源代码地址见下 源代码地址

自我介绍

其实说了这么多了还没进行自我介绍了。请看下面的: 个人logo

提问