深拷贝和浅拷贝


定义深拷贝、浅拷贝

不要相信其余人说的什么拷贝一层的是浅拷贝,若是拷贝后不影响原来对象内容的就是深拷贝,若是影响就是浅拷贝html

深拷贝通常不考虑基本数据类型(除了Object以外的),由于基本数据类型的简单的赋值就是深拷贝,因此在说深拷贝的时候只说引用数据类型jquery

下面说下引用数据类型的浅拷贝,引用数据类型的赋值就是栈内存地址的拷贝,a 和 b 中的栈使用的是相同的内存地址,指向同一个堆内存中的内容json

那么该如何实现深拷贝呢?(箭头表明指针,"child"表示子对象)就是拷贝一个对象使用不一样的栈地址开辟新的堆内存,可是堆内存中保存的内容是同样的就实现了深拷贝数组


深拷贝和浅拷贝的区别

1.浅拷贝: 将原对象或原数组的引用直接赋给新对象,新数组,新对象/数组只是原对象的一个引用bash

2.深拷贝: 建立一个新的对象和数组,将原对象的各项属性的“值”(数组的全部元素)拷贝过来,是“值”而不是“引用”数据结构

为何要使用深拷贝?

咱们但愿在改变新的数组(对象)的时候,不改变原数组(对象)函数

深拷贝的要求程度

咱们在使用深拷贝的时候,必定要弄清楚咱们对深拷贝的要求程度:是仅“深”拷贝第一层级的对象属性或数组元素,仍是递归拷贝全部层级的对象属性和数组元素?性能

怎么检验深拷贝成功

改变任意一个新对象/数组中的属性/元素, 都不改变原对象/数组ui

只作第一层深拷贝

深拷贝数组(只拷贝第一级数组元素) 

1.直接遍历


var arr = [1,2,3,4];


function copy(arg){
  
  var newArr = [];
  
  for(var i = 0; i < arr.length; i++) {
    newArr.push(arr[i]);
  }
  
  return newArr;
}

var newArry = copy(arr);
console.log(newArry);
newArry[0] = 10;
console.log(newArry); // [10,2,3,4]
console.log(arr)  // [1,2,3,4]

复制代码

2.slice()

var arr = [1,2,3,4]
var copyArr = arr.slice();
copyArr[0] = 10;
console.log(copyArr); // [10,2,3,4]
console.log(arr); // [1,2,3,4]
复制代码

slice() 方法返回一个从已有的数组中截取一部分元素片断组成的新数组(不改变原来的数组!) 用法:array.slice(start,end) start表示是起始元素的下标, end表示的是终止元素的下标spa

当slice()不带任何参数的时候,默认返回一个长度和原数组相同的新数组


3.concat()

var arr = [1,2,3,4]
var copyArr = arr.concat();
copyArr[0] = 10;
console.log(copyArr); // [10,2,3,4]
console.log(arr); // [1,2,3,4]
复制代码

concat() 方法用于链接两个或多个数组。( 该方法不会改变现有的数组,而仅仅会返回被链接数组的一个副本。)

用法:array.concat(array1,array2,......,arrayN)

由于咱们上面调用concat的时候没有带上参数,因此var copyArray = array.concat();实际上至关于var copyArray = array.concat([]); 也即把返回数组和一个空数组合并后返回

可是,事情固然不会这么简单,我上面的标题是 “深拷贝数组(只拷贝第一级数组元素)”,这里说的意思是对于一级数组元素是基本类型变量(如number,String,boolean)的简单数组, 上面这三种拷贝方式都能成功,但对第一级数组元素是对象或者数组等引用类型变量的数组,上面的三种方式都将失效,例如:

var arr = [
  {number:1},
  {number:2},
  {number:3}
  
]
var copyArr = arr.slice();
copyArr[0].number = 10;
console.log(copyArr);  // [{number: 100}, { number: 2 },{ number: 3 }]
console.log(arr); // [{number: 100}, { number: 2 }, { number: 3 }]
复制代码

深拷贝对象

1.直接遍历

var obj = {
    name: "张三",
    job: "学生"
  }
  
  function copy (arg) {
    let newobj = {}
    for(let item in obj) {
      newobj[item] = obj;
    }
    return newobj;
  }
  
  var copyobj = copy(obj)
  copyobj.name = "李四"
  console.log(copyobj) // {name: '李四', job:: '学生'}
  console.log(obj) // {name: '张三', job:: '学生'}
复制代码

2.ES6的Object.assign

var obj = {
  name: '张三',
  job: '学生'
}

var copyobj = Object.assign({},obj)
copyobj.name = '李四'
console.log(copyobj) // {name: '李四', job:: '学生'}
console.log(obj)    // {name: '张三', job:: '学生'}
复制代码

Object.assign:用于对象的合并,将源对象(source)的全部可枚举属性,复制到目标对象(target),并返回合并后的target

用法: Object.assign(target, source1, source2); 因此 copyObj = Object.assign({}, obj); 这段代码将会把obj中的一级属性都拷贝到 {}中,而后将其返回赋给copyObj


3.ES6扩展运算符:

var obj = {
  name: '张三',
  job: '学生'
}

var copyobj = {...obj}
copyobj.name = '李四'
console.log(copyobj)
console.log(obj)
复制代码

扩展运算符(...)用于取出参数对象的全部可遍历属性,拷贝到当前对象之中

对多层嵌套对象,很遗憾,上面三种方法,都会失败:

var obj = {
  name: {
    firstname: '张',
    lastname: '三'
  },
  job: '学生'
}

var copyobj = Object.assign({},obj)
copyobj.name.firstname = '王'
console.log(copyobj.name.firstname) // 王
console.log(obj.name.firstname)     // 王
复制代码

拷贝全部层级

有没有更强大一些的解决方案呢?使得咱们可以

1.不只拷贝第一层级,还可以拷贝数组或对象全部层级的各项值 2. 不是单独针对数组或对象,而是可以通用于数组,对象和其余复杂的JSON形式的对象

请看下面:

一、手动复制

把一个对象的属性复制给另外一个对象的属性

var obj1 = { a: 10, b: 20, c: 30 };
var obj2 = { a: obj1.a, b: obj1.b, c: obj1.c };
obj2.b = 100;
console.log(obj1);
// { a: 10, b: 20, c: 30 } <-- 沒被改到
console.log(obj2);
// { a: 10, b: 100, c: 30 }
但这样很麻烦,要一个一个本身复制;并且这样的本质也不能算是 Deep Copy,由于对象里面也可能回事对象,如像下面这个情况:

var obj1 = { body: { a: 10 } };
var obj2 = { body: obj1.body };
obj2.body.a = 20;
console.log(obj1);
// { body: { a: 20 } } <-- 被改到了
console.log(obj2);
// { body: { a: 20 } }
console.log(obj1 === obj2);
// false
console.log(obj1.body === obj2.body);
// true
虽然obj1跟obj2是不一样对象,但他们会共享同一个obj1.body,因此修改obj2.body.a时也会修改到旧的。
复制代码

二、对象只有一层的话可使用上面的:Object.assign()函数

Object.assign({}, obj1)的意思是先创建一个空对象{},接着把obj1中全部的属性复制过去,因此obj2会长得跟obj1同样,这时候再修改obj2.b也不会影响obj1。

由于Object.assign跟咱们手动复制的效果相同,因此同样只能处理深度只有一层的对象,没办法作到真正的 Deep Copy。不过若是要复制的对象只有一层的话能够考虑使用它。
复制代码

三、转成 JSON 再转回来

用JSON.stringify把对象转成字符串,再用JSON.parse把字符串转成新的对象。

var obj1 = { body: { a: 10 } };
var obj2 = JSON.parse(JSON.stringify(obj1));
obj2.body.a = 20;
console.log(obj1);
// { body: { a: 10 } } <-- 沒被改到
console.log(obj2);
// { body: { a: 20 } }
console.log(obj1 === obj2);
// false
console.log(obj1.body === obj2.body);
// false
这样作是真正的Deep Copy,这种方法简单易用。

可是这种方法也有很多坏处,譬如它会抛弃对象的constructor。也就是深拷贝以后,无论这个对象原来的构造函数是什么,在深拷贝以后都会变成Object。

这种方法能正确处理的对象只有 Number, String, Boolean, Array, 扁平对象,即那些可以被 json 直接表示的数据结构。RegExp对象是没法经过这种方式深拷贝。

也就是说,只有能够转成JSON格式的对象才能够这样用,像function没办法转成JSON。

var obj1 = { fun: function(){ console.log(123) } };
var obj2 = JSON.parse(JSON.stringify(obj1));
console.log(typeof obj1.fun);
// 'function'
console.log(typeof obj2.fun);
// 'undefined' <-- 没复制
要复制的function会直接消失,因此这个方法只能用在单纯只有数据的对象。

复制代码

四、递归拷贝

function deepClone(initalObj, finalObj) {    
  var obj = finalObj || {};    
  for (var i in initalObj) {        
    if (typeof initalObj[i] === 'object') {
      obj[i] = (initalObj[i].constructor === Array) ? [] : {};            
      arguments.callee(initalObj[i], obj[i]);
    } else {
      obj[i] = initalObj[i];
    }
  }    
  return obj;
}
var str = {};
var obj = { a: {a: "hello", b: 21} };
deepClone(obj, str);
console.log(str.a);
复制代码

上述代码确实能够实现深拷贝。可是当遇到两个互相引用的对象,会出现死循环的状况。

为了不相互引用的对象致使死循环的状况,则应该在遍历的时候判断是否相互引用对象,若是是则退出循环。

改进版代码以下:

function deepClone(initalObj, finalObj) {    
  var obj = finalObj || {};    
  for (var i in initalObj) {        
    var prop = initalObj[i];        // 避免相互引用对象致使死循环,如initalObj.a = initalObj的状况
    if(prop === obj) {            
      continue;
    }        
    if (typeof prop === 'object') {
      obj[i] = (prop.constructor === Array) ? [] : {};            
      arguments.callee(prop, obj[i]);
    } else {
      obj[i] = prop;
    }
  }    
  return obj;
}
var str = {};
var obj = { a: {a: "hello", b: 21} };
deepClone(obj, str);
console.log(str.a);
复制代码

五、使用Object.create()方法

直接使用var newObj = Object.create(oldObj),能够达到深拷贝的效果。

function deepClone(initalObj, finalObj) {    
  var obj = finalObj || {};    
  for (var i in initalObj) {        
    var prop = initalObj[i];        // 避免相互引用对象致使死循环,如initalObj.a = initalObj的状况
    if(prop === obj) {            
      continue;
    }        
    if (typeof prop === 'object') {
      obj[i] = (prop.constructor === Array) ? [] : Object.create(prop);
    } else {
      obj[i] = prop;
    }
  }    
  return obj;
}
复制代码

六、jquery

jquery 有提供一个$.extend能够用来作 Deep Copy。

var $ = require('jquery');
var obj1 = {
    a: 1,
    b: { f: { g: 1 } },
    c: [1, 2, 3]
};
var obj2 = $.extend(true, {}, obj1);
console.log(obj1.b.f === obj2.b.f);
// false
复制代码

七、lodash

另一个很热门的函数库lodash,也有提供_.cloneDeep用来作 Deep Copy。

var _ = require('lodash');
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
复制代码

这个性能还不错,使用起来也很简单。

存在大量深拷贝需求的代码——immutable提供的解决方案

实际上,即便咱们知道了如何在各类状况下进行深拷贝,咱们也仍然面临一些问题: 深拷贝其实是很消耗性能的。(咱们可能只是但愿改变新数组里的其中一个元素的时候不影响原数组,但却被迫要把整个原数组都拷贝一遍,这不是一种浪费吗?)因此,当你的项目里有大量深拷贝需求的时候,性能就可能造成了一个制约的瓶颈了。

immutable的做用: 经过immutable引入的一套API,实现:

1.在改变新的数组(对象)的时候,不改变原数组(对象) 2.在大量深拷贝操做中显著地减小性能消耗

先睹为快:

const { Map } = require('immutable')
const map1 = Map({ a: 1, b: 2, c: 3 })
const map2 = map1.set('b', 50)
map1.get('b') // 2
map2.get('b') // 50
复制代码

参考地址1

参考地址2

相关文章
相关标签/搜索