前端深拷贝浅拷贝

173 阅读5分钟

在了解深拷贝,和浅拷贝区别之前需要先了解, 基本数据类型和引用数据类型的区别。

一、 基本数据类型和引用数据类型的区别:

  1. 保存位置不同:基本数据类型保存在的内存中, 引用数据类型保存在的内存中,然后在栈内存中,保存了一个对堆内存中实际对象的引用,即数据在堆内存中的地址,js 对引用数据类型的操作都是操作对象的引用而不是实际对象,如果 obj1 拷贝了 obj2 ,那么这两个引用数据类型就指向了同一个堆内存对象,具体操作是 obj1 将栈内存的引用地址复制了一份给 obj2,因二他们共同指向了一个堆内存对象。 为什么基本数据类型保存在栈中, 而数据类型保存在堆中?

    1. 堆比栈大, 栈比堆速度快
    2. 基本数据类型较为稳定, 相对来说占用内存较小
    1. 引用数据类型大小是动态的, 而且是无限的, 引用值的大小会改变, 不能把他放在栈中, 否则会降低变量查找的速度,因此放在变量栈空间的值是该对象存储在堆中的地址,地址的大小是固定的,所以把它存储在栈中对变量性能无任何负面影响。
    2. 堆内存是无序存放,可以根据引用直接获取

按引用访问,JS 不允许直接访问在堆中的对象, 所以在访问一个对象时,首先得到的是这个对象存放在堆内存中的地址, 然后在根据这个地址去获取对象中的值。

ECMAScript 中所有函数的参数都是按值来传递,对于原始值, 只是把变量里的值传递给参数,之后参数和这个变量是互不影响的,对于引用值, 对象变量里面的值是这个对象在堆内存中的内存地址, 因此他传递的值也就是这个内存地址,这也就是为什么函数内部对这个参数的修改, 会体现在外部的原因, 因为他们都指向同一个对象

2.基本数据类型使用 typeof 可以返回其基本数据类型,但是 null 类型会返回 object, 因此 null 表示一个空对象指针,引用数据类型使用 typeof 会返回 object, 此时需要使用 instanceof 来检测引用数据类型;

3.定义引用数据类型需要使用 new 操作符, 后面再跟有一个构造函数来构建:

1) 使用 new 操作符来创建对象

var obj1  =  new Object()
obj1.a = 1
  1. 使用对象字面量来创建对象
var obj2 = {
   a = 2
}

3) 可以使用点表示法访问对象的属性, 也可以使用方括号表示法来访问对象的属性。

二、js 浅拷贝

image.png

  • 深拷贝和浅拷贝简单解释

    深拷贝和浅拷贝都只针对于引用数据类型,浅拷贝, 只复制指向某个对象的指针,而不复制对象本身,新旧对象还是共享同一块儿内存,但深拷贝会另外创造一个一模一样的对象, 新对象跟原对象不共享内存,修改新对象不会改变原对象 。

    区别:浅拷贝只复制对象的第一层属性,深拷贝可以对对象的属性进行递归复制:

    // 只复制第一层的浅拷贝
    function symbolCopy(obj1){
      var obj2 = Array.isArray(obj1) ? []: {};
      for(let i in obj1){
        obj2[i]  = obj1[i]
      }
      return obj2
    }
    
    var obj1 = {
        a:1,
        b:2,
        c:{
        d:3
        }
    }
    
    var obj3 = symbolCopy(obj1);
    obj3.a = 3
    obj3.c.d = 4
    
    console.log(obj1.a)  // 1
    console.log(obj3.a)  // 3
    console.log(obj1.c.d) // 4
    console.log(obj3.c.d) // 4
    
  • object.assign() 实现浅拷贝及一层的深拷贝

    let obj1 = {
        a: {
          b:1
        },
        c: 2
    }
    let obj2 = Object.assign({},obj1)
    obj2.a.b = 3
    obj2.c = 3 
    console.log(obj1.a.b) // 3
    console.log(obj2.a.b) // 3
    console.log(obj1.c) // 2
    console.log(obj2.c) // 3

三、js 深拷贝

  • 手动实现深拷贝
let obj1 = {
    a: 1,
    b: 2
}
let obj2 = {
    a: obj1.a,
    b: obj2.b
}
obj2.a = 3
console.log(obj1.a) // 1
console.log(obj2.a) // 3
let obj1 = {
    a: {
       b:1
    }
 }
 let obj2 = {
     a : obj1.a
 }
 obj2.a.b = 3
 console.log(obj1.a.b)   // 3
 console.log(obj2.a.b)   // 3
  • 递归实现深拷贝
function deepCopy(obj1){
    let obj2 =  array.isArray(obj1) ? [] : {};
    if(obj1 && typeof obj1 === "object"){
       for(let i in obj1){
           if(obj1.hasOwnProperty(i)){
          
           if(obj1[i] && typeof obj1[i] === "object"){
              // 如果子属性为引用数据类型, 递归复制
              obj2[i]=  deepCopy(obj1[i])
          }else{
            obj2[i] = obj1[i]
          }  
         }
       }
   }
   return obj2
}
let obj1 = { 
    a:1,
    b:2,
    c:{
     d:3
    }
 }
let obj2 = deepCopy(obj1)
obj2.a = 4
obj2.c.d = 4

consol.log(obj1.a) // 1
consol.log(obj2.a) // 4
console.log(obj1.c.d) // 3
console.log(obj2.c.d) // 4

缺陷:当遇到两个互相引用的对象的对象,会出现死循环的情况 , 为了避免死循环情况的发生, 需要在遍历的时候先判断是否相互引用对象, 如果是则退出循环

    function deepCopy(obj){
        var obj2 = Array.isArray(obj) ? []:{}
        if(obj && typeof obj === "object"){
            for(i in obj){
                var prop = obj[i]
                if(prop == obj){
                }
                if(obj.hasOwnProperty(i)){
                    if(prop && typeof prop === "object"){
                        obj2[i] = (prop.constructor === Array)? []: {}
                        arguments.callee(prop,obj2[i]); // 递归调用
                    }else{
                        // 如果是基本数据类型, 只是简单的复制
                        obj2[i] = prop
                    }
                }
            }
        }
        return obj2
    }
    
    let obj1 = { 
    a:1,
    b:2,
    c:{
     d:3
    }
 }
let obj2 = deepCopy(obj1)
obj2.a = 4
obj2.c.d = 4

consol.log(obj1.a) // 1
consol.log(obj2.a) // 4
console.log(obj1.c.d) // 3
console.log(obj2.c.d) // 4

  • 使用 JSON.stringify() 和 JSON.parse() 实现深拷贝, 先用 JSON.stringify() 把对象转换成字符串就行赋值,再使用 JSON.parse() 把字符串转换成对象。
  function deepCopy(obj1){
     let _obj1 = JSON.stringify(obj1)
     let boj2 = JSON.parse(_obj1)
     return obj2
  }
  var a = [1,[1,2],3,4]
  var b = deepCopy(a)
  console.log(a)  // [1,[1,2],3,4]
  console.log(b)  // [1,[1,2],3,4]

缺陷:它会抛弃原对象的 constructor 深拷贝之后, 不过原来的构造函数是什么,在深拷贝之后都会变成 Object, 这种方法能正确处理的对象只有 Number, String, boolean, Array 扁平对象, 也就是说只有能转成 JSON 格式的对象才才可以这样用, 像 function 没办法转成 JSON

    let obj1 = {
        fun: function(){
            console.log(123)
        }
    }
    let obj2 = JSON.parse(JSON.stringify(obj1))
    console.log(obj1) // 123
    console.log(obj2) // 123
  • 热门函数库 loadsh, 也有提供_.cloneDeep 用来做深拷贝
var _ = require("loadsh")
var obj1 = {
    a:1,
    b: {
    f:{
      g:1
     }
     c: [1,2,3]
    }
}

var obj2 = _.cloneDeep(obj1)
console.log(obj1.b.f === obj2.b.f) // false

  • slice()和concat()都并非深拷贝