JavaScript中常见的深、浅拷贝的方法小结

155 阅读4分钟

几个概念:栈堆,基本数据类型与引用数据类型

基本数据类型存放在栈中,引用类型存放在堆中。

  • 基本数据类型: 字符串(String)、数字(Number)、布尔(Boolean)、对空(Null)、未定义(Undefined)、Symbol。
  • 引用数据类型:对象(Object)、数组(Array)、函数(Function)。
什么是堆栈

堆栈都是一种数据项按序排列的数据结构,只能在一端(称为栈顶(top))对数据项进行插入和删除。

  • 栈(stack)为自动分配的内存空间,它由系统自动释放;
  • 堆(heap)则是动态分配的内存,大小不定也不会自动释放。
堆和栈的区别:

1,堆,队列优先,先进先出(FIFO—first in first out)
2,栈,先进后出(FILO—First-In/Last-Out)


什么是深拷贝、浅拷贝

1,是指拷贝一个对象时,不仅仅把对象的引用进行复制,还把该对象引用的值也一起拷贝
2,源对象与拷贝对象互相独立,其中任何一个对象的改动都不会对另外一个对象造成影响

深拷贝和浅拷贝简单解释

  • 浅拷贝和深拷贝都只针对于引用数据类型,浅拷贝只复制指向某个对象的指针,而不复制对象本身,新旧对象还是共享同一块内存;但深拷贝会另外创造一个一模一样的对象,新对象跟原对象不共享内存,修改新对象不会改到原对象;
  • 区别:浅拷贝只复制对象的第一层属性、深拷贝可以对对象的属性进行递归复制

深拷贝常用方法

  • 用 JSON.stringify 把对象转成字符串,再用 JSON.parse 把字符串转成新的对象(使用JSON)。
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

坏处:它会抛弃对象的constructor。也就是深拷贝之后,不管这个对象原来的构造函数是什么,在深拷贝之后都会变成Object。所以只适合 Number, String, Boolean, Array 的扁平对象

  • 递归拷贝
function deepClone(obj) {
  let objClone = Array.isArray(obj) ? [] : {};
  if (obj && typeof obj === "object") {
    for (key in obj) {
      if (obj.hasOwnProperty(key)) {
        //判断ojb子元素是否为对象,如果是,递归复制
        if (obj[key] && typeof obj[key] === "object") {
          objClone[key] = deepClone(obj[key]);
        } else {
          //如果不是,简单复制
          objClone[key] = obj[key];
        }
      }
    }
  }
  return objClone;
} 

var obj1 = {
   a: 1,
   b: 2,
   c: {
     d: 3
   }
}
var obj2 = deepClone(obj1);
obj2.a = 3;
obj2.c.d = 4;
alert(obj1.a); // 1
alert(obj2.a); // 3
alert(obj1.c.d); // 3
alert(obj2.c.d); // 4
  • 使用ES6中的 Object.create()方法
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;
}
  • 热门的函数库lodash,也有提供_.cloneDeep用来做深拷贝。
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
  • JQ的extend方法
//$.extend( [deep ], target, object1 [, objectN ] )
//deep表示是否深拷贝,为true为深拷贝,为false,则为浅拷贝
//target Object类型 目标对象,其他对象的成员属性将被附加到该对象上。
//object1  objectN可选。 Object类型 第一个以及第N个被合并的对象。 
 
let a=[0,1,[2,3],4],
    b=$.extend(true,[],a);
a[0]=1;
a[2][0]=1;
console.log(a,b);
深拷贝应用场景:不对原数据对象造成影响

1、从服务器fetch到数据之后我将其存放在store中,通过props传递给界面,然后我需要对这堆数据进行修改,那涉及到修改就一定有保存和取消,所以我们需要将这堆数据拷贝到其他地方(网友的经历)
2、当你想使用某个对象的值,在修改时不想修改原对象,那么可以用深拷贝来弄一个新的内存对象。


浅拷贝常用方法

  • 直接赋值(=)
let a = {
  b:1,
  m:'p'
}
 
const acl = a
  • ES6的Object.assign()
let o ={name: {asd: '123'}}
let p = Object.assign({}, o)
p.name = '123456789'
console.log(p, o);//{name: "123456789"},{name: {asd: "123"}}

1、Object.assign()用于将所有可枚举属性的值从一个或多个源对象复制到目标对象。
2、Object.assign进行的拷贝是浅拷贝,及只有第一层是深拷贝。若拷贝的属性的值是对象的复合属性,就只是拷贝过来一个引用。

  • ES6扩展运算符( ... )
let a={title:{info:"二级属性"}};
let b={...a,content:"一个教程"};
b.title.info = '二级属性都被修改了'
console.log(a.title);
console.log(b.title);

注意点

  • slice()和concat()都并非深拷贝
// 对只有一级属性值的数组对象使用slice
var a = [1,2,3,4];
var b = a.slice();
b[0] = 2;
alert(a); // 1,2,3,4
alert(b); // 2,2,3,4
// 对有多层属性的数组对象使用slice
var a = [1,[1,2],3,4];
var b = a.slice();
b[1][0] = 2;
alert(a); // 1,2,2,3,4
alert(b); // 1,2,2,3,4

从上面两个例子可以看出,由于数组内部属性值为引用对象,因此使用slice和concat对对象数组的拷贝,整个拷贝还是浅拷贝,拷贝之后数组各个值的指针还是指向相同的存储地址。因此,slice和concat这两个方法,仅适用于对不包含引用对象的一维数组的深拷贝