JavaScript 浅克隆 和 深克隆 解析

239 阅读4分钟

浅克隆

概念:只复制对象第一级属性值。如果对象的第一级属性中又包含引用类型,则只复制地址。

问题:如果对象中又包含引用类型的属性值,则导致克隆后,新旧对象依然共用同一个引用类型的对象属性值。

结果:任意一方修改了引用类型的对象内容,都会导致另一方同时受影响。如下代码

(一):Object.assign()

    var obj = {a:1,b:2,c:{d:10,e:20}}
    var obj1 = {}
    
    Object.assign(obj1,obj) //克隆
    
    obj.a = 2;
    obj.c.d = 100
    console.log(obj)  // {a:2,b:2,c:{d:100,e:20}}
    console.log(obj1) // {a:1,b:2,c:{d:100,e:20}}

(二):展开运算符(...)

    var obj = {a: 1,b: 2,c: {d: 10,e: 20}}
    var obj1 = {}
    
    obj1 = {...obj} //克隆
    
    obj.a = 2;
    obj.c.d = 100
    console.log(obj) // {a:2,b:2,c:{d:100,e:20}}
    console.log(obj1) // {a:1,b:2,c:{d:100,e:20}}

(三):函数库lodash的_.clone方法

    var obj = {a: 1,b: 2,c: {d: 10,e: 20}}
    var obj1 = {}
    
    obj1 = _.clone(obj) //克隆
    
    obj.a = 2;
    obj.c.d = 100
    console.log(obj) // {a:2,b:2,c:{d:100,e:20}}
    console.log(obj1) // {a:1,b:2,c:{d:100,e:20}}

深克隆

概念:不但复制对象的第一级属性值,而且,即使对象中又包含引用类型的属性值,深克隆也会继续复制内嵌类型的属性值。

结果: 克隆后,两个对象彻底再无关。

(一):JSON.parse(JSON.stringify())

   var obj = {a:1,b:2,c:{d:10,e:20}}
    var obj1 = {}
    
    obj1=JSON.parse(JSON.stringify (obj)) //克隆
    
    obj.a = 2;
    obj.c.d = 100
    console.log(obj)  // {a:2,b:2,c:{d:100,e:20}}
    console.log(obj1) // {a:1,b:2,c:{d:10,e:20}}

弊端:以下两种情况无法实现深拷贝

  • obj 对象种加入 set get 方法,会导致方法被运算
  • obj 对象通过 Object.defineProperty() 添加属性时无法被拷贝
    Object.defineProperty(obj,"h",{
        value :30
    })

(二):递归调用

复习一下递归:程序调用自身的编程技巧称为递归(recursion)。

     function factorial(n) { 
         if (n == 1) return n; 
         return n * factorial(n - 1) 
     } 
     console.log(factorial(5)) // 5 * 4 * 3 * 2 * 1 = 120

构成递归需具备边界条件、递归前进段和递归返回段,当边界条件不满足时,递归前进,当边界条件满足时,递归返回。阶乘中的 n == 1 就是边界条件。

方法一
    
    function deepClone(target) {
      let newObj; // 定义一个变量,准备接新副本对象
      // 如果当前需要深拷贝的是一个引用类型对象
      if (typeof target === 'object') {
        if (Array.isArray(target)) { // 如果是一个数组
          newObj = []; // 将newObj赋值为一个数组,并遍历
          for (let i in target) { // 递归克隆数组中的每一项
            newObj.push(deepClone(target[i]))
          }
          // 判断如果当前的值是null;直接赋值为null 
        } else if (target === null) {
          newObj = null;
          // 判断如果当前的值是一个正则表达式对象,直接赋值
        } else if (target.constructor === RegExp) {  
          newObj = target;
        } else {
          // 否则是普通对象,直接for in循环递归遍历复制对象中每个属性值
          newObj = {};
          for (let i in target) {
            newObj[i] = deepClone(target[i]);
          }
        }
        // 如果不是对象而是原始数据类型,那么直接赋值
      } else {
        newObj = target;
      }
      // 返回最终结果 
      return newObj;
    }
    
    //调用
    var arr= [1,2,3,{name:'zs',age:14}]
    
    var arr1=deepClone(arr)  //克隆
    
    arr[1]=10;
    arr[3].name='lisi'
    console.log(arr)  // [10,2,3,{name:'lisi',age:14}]
    console.log(arr1) // [1,2,3,{name:'zs',age:14}]
方法二
var deepCopy = function(obj) {
    if (typeof obj !== 'object') return;
    var newObj = obj instanceof Array ? [] : {};
    for (var key in obj) {
        if (obj.hasOwnProperty(key)) {
            newObj[key] = typeof obj[key] === 'object' ? deepCopy(obj[key]) : obj[key];
        }
    }
    return newObj;
}

解析(一)typeof

我之前一直以为如果target为数组,则 输出Array; 其实输出的是 Object;因为Array 也是特殊的Object

typeof "John"                // 返回 string
typeof 3.14                  // 返回 number
typeof false                 // 返回 boolean
typeof [1,2,3,4]             // 返回 object
typeof {name:'John'// 返回 object  
typeof undefined             // undefined
typeof null                  // object
null === undefined           // false
null == undefined            // true  

解析(二).constructor

概念:属性返回对创建此对象的数组函数的引用。

不懂的可以参照:www.lccee.com/web/jsref/j…

解析(三)instanceof

概念:instanceof运算符测试构造函数的属性是否 出现prototype在对象的原型链中的任何位置。返回值是一个布尔值。

不懂的可以参照:developer.mozilla.org/en-US/docs/…

解析(四)hasOwnProperty

概念:hasOwnProperty()  方法会返回一个布尔值,指示对象自身属性中是否具有指定的属性(也就是,是否有指定的键)

不懂的可以参照:developer.mozilla.org/zh-CN/docs/…