简单来了解js的赋值,浅拷贝和深拷贝

这是我参与更文挑战的第21天,活动详情查看: 更文挑战

前言

在工作中我们经常会给变量赋值另一个变量,也经常为了不污染原始数据,需要对原始数据做拷贝处理,拷贝又分浅拷贝和深拷贝;你们知道哪些方法是浅拷贝?哪些方法是深拷贝?如果让你手写一个深拷贝,你可以很快写出来吗?下面来讲讲它们。

数据类型

在讲讲拷贝之前,我们先讲讲js的数据类型:

一共有2种,分为基本类型引用类型

  • 基本类型String, Number, Boolean, Undefined, Null, Symbol(新引入类型), BigInt(新引入类型)
  • 引用类型Object(其它的比如Array, Function等都属于Object类型)

基本类型的值是存储在栈内存中,占据固定大小,不可变;
引用类型的值是存储在堆内存中,大小不定,可变,它在栈内存中存储的是固定大小的地址(不可变),指向堆内存中的值;

我们对数据操作一般有三种方式, 赋值, 浅拷贝, 深拷贝。下面一一来分析。

赋值

对基本类型的赋值,就是在栈内存中分配一块新的空间给新的变量,二者互相没有影响。
对引用类型的赋值,就是在栈内存重新复制一份地址给新的变量,用以指向同一个堆内存的值。所以如果堆内存的值改变,二者的值都会改变。

下面可以通过例子来看一下:

let a = '答案cp3'
let b =  a
console.log(a, b) // 答案cp3  答案cp3
b = 123
console.log(a, b) // 答案cp3  123
复制代码

由此可见,基本类型的赋值 改变新的变量不会影响原来的变量

let obj = {name: ''}
let obj1 = obj
console.log(obj, obj1) // {name: ''}  {name: ''}
obj.name = '答案cp3'
console.log(obj, obj1) // {name: '答案cp3'}  {name: '答案cp3'}
复制代码

由此可见,引用类型的赋值,如果改变新的变量,会影响原来的变量,因为他们栈内存指向的是同一个对象值;

浅拷贝

浅拷贝 主要是针对引用类型,是对引用类型的一次复制。如果属性值是基本类型,则是复制基本类型的值,如果属性值是引用类型,则是复制引用类型的栈地址;如果改变对应的堆内存中的值,则双方都会改变。

下面看个例子:

let obj = {name: '答案', info: {age: 18}}
let obj1 = Object.assign({}, obj)
obj1.name = '答案cp3'
obj1.info.age = 20
console.log(obj1, obj) 
// obj1: {name: "答案cp3", info: {age: 20}} 
// obj: {name: "答案", info: {age: 20}}
复制代码

可以看到浅拷贝如果属性是基本类型,则改变不会影响对方,如果是引用类型,则会影响对方。

深拷贝

深拷贝,也是针对引用类型,是指把引用类型的完全复制,不管属性值是基本类型还是引用类型,深拷贝出来的对象,不管怎么改变自身的属性,不会影响原来的对象,二者是两个独立的互不影响的。

深拷贝一般有2种方式。

  1. JSON.stringify

    先把对象字符串化,最后再调用JSON.parse解析成对象

    let obj = {name: '答案', info: {age: 18}}
    let obj1 = JSON.parse(JSON.stringify(obj))
    obj1.name = '答案cp3'
    obj1.info.age = 20
    console.log(obj1, obj)
    // obj1: {name: "答案cp3", info: {age: 20}} 
    // obj: {name: "答案", info: {age: 18}}
    复制代码

    可以看到是深拷贝是成功的,不管你改变哪个值,都不会影响原来的对象。

    但是JSON.stringify有个缺点,他对一些属性值就不能拷贝,比如undefined,function这些,不能很好的兼容。

    let obj = {name: undefined, info: {age: 18}}
    let obj1 = JSON.parse(JSON.stringify(obj))
    console.log(obj1, obj)
    // obj1:{info:{age: 18}} name被忽略了
    // obj:{name: undefined, info: {age: 18}}
    复制代码
    let obj = {name: function () {}, info: {age: 18}}
    let obj1 = JSON.parse(JSON.stringify(obj))
    console.log(obj1, obj)
    // obj1:{info:{age: 18}}  name被忽略了
    // obj:{name: function () {}, info: {age: 18}}
    复制代码
  2. 递归遍历复制

    这个是值得推荐的,基本能兼容各个数据类型。

    function deepClone (target) {
      if (typeof target !== 'object' || target === null) return target
      const copyTarget = Array.isArray(target) ? [] : {}
      for (let key in target) {
        if (typeof target[key] === 'object') {
          copyTarget[key] = deepClone(target[key])
        } else {
          copyTarget[key] = target[key]
        }
      }
      return copyTarget
    }
    let obj = {name: '答案', info: {age: 18}}
    let obj1 = deepClone(obj)
    obj1.name = '答案cp3'
    obj1.info.age = 20
    console.log(obj1, obj)
    // obj1:{name: ‘答案cp3’, info:{age: 20}}
    // obj:{name: ‘答案’, info: {age: 18}}
    
    let obj = {name: undefined, info: {age: 18}}
    let obj1 = deepClone(obj)
    console.log(obj1, obj)
    // obj1:{name: undefined, info:{age: 18}}
    // obj: {name: undefined, info: {age: 18}}
    
    let obj = {name: function (){}, info: {age: 18}}
    let obj1 = deepClone(obj)
    console.log(obj1, obj)
    // obj1:{name: function (){}, info:{age: 18}}
    // obj: {name: function (){}, info: {age: 18}}
    复制代码

    主要原理是利用遍历对象,如果属性值是基本类型,则直接赋值,如果是引用类型,则递归遍历,直到属性值是基本类型。

    这里还要考虑循环利用的情况,如果不考虑的话,可能会导致死循环,栈溢出

    image.png

    这时候需要用到Map这种数据结构,因为他可以用对象做key, 把已经循环遍历的引用对象用Map存储起来,下次循环遍历的时候通过Map判断,如果Map的key有这个对象则直接return,不继续遍历了。

    代码如下:

     function deepClone (target, map = new Map()) {
      if (typeof target !== 'object' || target === null) return target
      if (map.get(target)) return map.get(target)
      const copyTarget = Array.isArray(target) ? [] : {}
      map.set(target, copyTarget)
      for (let key in target) {
        if (typeof target[key] === 'object') {
          copyTarget[key] = this.deepClone(target[key], map)
        } else {
          copyTarget[key] = target[key]
        }
      }
      return copyTarget
    }
    let obj = {name: function (){}, info: {age: 18}}
    obj.obj = obj
    let obj1 = deepClone(obj)
    console.log(obj1, obj)
    // obj1: {info: {…}, obj: {…}, name: ƒ} 
    // obj:  {info: {…}, obj: {…}, name: ƒ} 
    复制代码

    运行成功~

总结

以上就是我总结的js的赋值,浅拷贝和深拷贝的知识,希望对你们有帮助~

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享