Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

手写深拷贝 #45

Open
18888628835 opened this issue Jun 13, 2021 · 0 comments
Open

手写深拷贝 #45

18888628835 opened this issue Jun 13, 2021 · 0 comments

Comments

@18888628835
Copy link
Owner

18888628835 commented Jun 13, 2021

手写深拷贝

深拷贝是什么

在JS中,所有的拷贝API都是浅拷贝,比如数组的拷贝,我们一般使用Array.prototype.slice来拷贝一个数组,但是对于嵌套数组,就会只拷贝其中的引用。

const arr=[[1,2,3],[4,5,6]]
const arr2=arr.slice()
arr2[0].push(666)
console.log(arr) //[[1, 2, 3, 666], [4, 5, 6]]

上面的arr2是拷贝后的数组,arr2改变了同样会影响到arr的值,所以这就不是深拷贝。

官方解释是这样的

将一个对象从内存中完整的拷贝一份出来,从堆内存中开辟一个新的区域存放新对象,且修改新对象不会影响原对象

我所理解的深拷贝,就是当我克隆一个东西出来之后,跟原来的完全不相交。

序列化与反序列化的深拷贝

在工作中,我们一般都会使用序列化进行深拷贝,就是采用JSON.stringifyJSON.parse来进行深拷贝

const obj={
   name:'yanxi',
   props:{name:'qiu'}
}
const obj2=JSON.parse(JSON.stringify(obj))
obj2.props.name='11111'
obj 
//{name: "yanxi", props: {name: "qiu"}}

这种方式非常简单,但JSON的局限性较大。

局限性如下:

1、不支持函数,会自动忽略

2、不支持undefined,JSON天然不支持

3、不支持环状引用(即引用自身)会报错

4、不支持Date,会转成字符串

5、不支持symbol,JSON天然不支持

我们可以看到JSON.parse(JSON.stringify())虽然能够深拷贝一个对象,但是存在很大的局限性,对于复杂的对象就不适用了。因此,我们需要采用另外的方式来实现深拷贝,也就是通过递归的方式手动实现深拷贝。

递归深拷贝

JS中存在七种类型:number、string、boolean、symbol、null、undefined、object

其中除了object属于引用类型,其余都是简单数据类型,所以我们主要要对object的数据类型进行分辨,其余的简单数据类型都只要直接返回即可。

知道了深拷贝的概念,有了深拷贝的思路

下面我们从0开始,手撸一个深拷贝

简单数据类型深拷贝

简单数据是不可变的

let a=1
let b=a
b=2
a //1

上面的代码只是改变了b的指向,并不影响原来的数据a。

那么我们就可以直接实现对于简单数据类型的深拷贝函数

function deepClone(target){
   return target
}

object 类型深拷贝

深拷贝中最重要的是解决 object 类型深拷贝的思路,基于这个复杂数据类型,我们需要对Javascript 中的各种对象(数组对象,函数对象等)进行深拷贝的实现。

function cloneDeep(target) {
  let result
  if (typeof target === 'object') {
    result = {}
    for (let key in target) {
      result[key] = cloneDeep(target[key])
    }
    return result
  }
  return target
}

上面的代码通过对类型的检测,如果发现传入的是一个 object 对象,那么就循环它的属性,并且通过递归的方式一一将 object 对象的属性打到新创建的空白对象中,并将其返回。

Array 类型的深拷贝

在对数组类型进行检测的时候,我们不能用 typeof 进行检测,因为会返回 object,这里我们需要使用的Object.prototype.toString.call(Array)或者instanceof关键字,为了方便,这里就使用 instanceof 关键字

function cloneDeep(target) {
  let result
  if (typeof target === 'object') {
    if (target instanceof Array) {
      result = []
      for (let key of target) {
        result.push(cloneDeep(key))
      }
    } else {
      result = {}
      for (let key in target) {
        result[key] = cloneDeep(target[key])
      }
    }
    return result
  }
  return target
}

函数深拷贝

函数需要怎么深拷贝呢?我们知道函数中的参数传递都是值传递,虽然目标函数会改变引用地址,但是函数已经把值作为参数传递进去了,所以我们直接返回调用函数的结果就可以了。

let fn=function (){return 123}
cloneDeep(fn) / /
fn=function (){return 456} // 这里的函数改变了引用地址
cloneDeep(fn)() //需要做到返回123

下面是实现代码,为了维持代码的结构,让简单数据类型和复杂数据类型做分离,我使用 instanceof 检测 target 类型,当检测函数时才使用 typeof 关键字

function cloneDeep(target) {
  let result
  if (target instanceof Object) {
    if (target instanceof Array) {
      result = []
      for (let key of target) {
        result.push(cloneDeep(key))
      }
    } else if (typeof target === 'function') {
      result = function(...rest) {
        return target(...rest)
      }
    } else {
      result = {}
      for (let key in target) {
        result[key] = cloneDeep(target[key])
      }
    }
    return result
  }
  return target
}

还有更多的数据对象如果想要实现深拷贝,只要新增类型判断即可,这里就不再扩展了。

结束~

enjoy!

@18888628835 18888628835 changed the title 手写深拷贝 实现一个深拷贝 Jun 13, 2021
@18888628835 18888628835 changed the title 实现一个深拷贝 手写深拷贝 Jun 17, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant