JavaScript的深拷贝与浅拷贝

今天,在阅读《红宝书》第8章的理解对象的"合并对象"时,发现书中提到了:Object.assign()实际上对每个源对象执行的是浅复制。

这也是面试高频知识点,现整理下关于JavaScript的深拷贝与浅拷贝。

预备知识

  • 在你想要了解深拷贝与浅拷贝的时候,你知道:

    • JavaScript中的数据类型有哪些?
    • 这些类型是如何进行存储的?
  • 如果你知道这些,你的基础还不错;不知道或知道的不全,也没关系,现在学起来不晚,加油。

  • 搜索关键字JS的数据类型JS数据类型的存储方式去学习吧,也可参考我的笔记

  • 还可以参考 JS的深拷贝和浅拷贝详解JS深拷贝与浅拷贝中的相关介绍,本文不再赘述了。

初识概念

  • 以下几种不同的解释,仅供参考与理解。
    • 如何区分深拷贝与浅拷贝,简单点来说,就是假设B复制了A,当修改A时,看B是否会发生变化,如果B也跟着变了,说明这是浅拷贝,拿人手短,如果B没变,那就是深拷贝,自食其力。
    • 深拷贝和浅拷贝都只针对引用数据类型,浅拷贝会对对象逐个成员依次拷贝,但只复制内存地址,而不复制对象本身,新旧对象成员还是共享同一内存;深拷贝会另外创建一个一模一样的对象,新对象跟原对象不共享内存,修改新对象不会改到原对象。区别:浅拷贝只复制对象的第一层属性,而深拷贝会对对象的属性进行递归复制。
    • 我的理解:如果一个对象Obj2复制了Obj1,当修改Obj2的时候,Obj1也发生了变化,就是浅拷贝;如果Obj1没有发生变化,那么就是深拷贝
          // 1 定义一个Obj1对象
          var obj1 = {
      	  name: "张三";
      	  age: 20;
      	};
      	// 2 将obj1赋值给boj2
      	var obj2 = obj1;
      	// 3 修改obj2
      	obj2.name= "李四";
      	console.log(obj1.name);  // 李四
      	console.log(obj2.name);  // 李四
      	你会发现,两个对象的name都变了,所以这是什么拷贝?
      	没错,这个是浅拷贝,obj2改变,obj1也变化了。
      

浅拷贝

  • 常见的浅拷贝有:
    • Object.assign()
    • Array.prototype.concat()
    • Array.prototype.slice()
    • 扩展运算符
    • ☆手写浅拷贝

Object.assign()

ES6提供了Object.assign()方法,这个方法接收一个目标对象和一个或多个源对象作为参数,将所有可枚举的自有属性从一个或多个源对象复制到目标对象,返回修改后的对象。

	let dest, src, result;
	/** 简单复制 */
	dest = {};
	src = { id: 'src' };
	result = Object.assign(dest, src);
	// Object.assign 修改目标对象,也会返回修改后的目标对象
	console.log(dest === result); // true
	console.log(dest !== src); // true
	console.log(result); // { id: src }
	console.log(dest); // { id: src }
	/** 多个源对象 */
	dest = {};
	result = Object.assign(dest, { a: 'foo' }, { b: 'bar' });
	console.log(result); // { a: foo, b: bar }

Array.prototype.concat()

concat() 方法用于合并两个或多个数组。此方法不会更改现有数组,而是返回一个新数组。

	const arr1 = [1,2,3, {a: 4}]
	const arr2 = arr1.concat([5])
	
	arr2[0] = 2022
	arr2[3].a = '你好世界'
	console.log(arr1) // [1, 2, 3, Object { a: "你好世界" }]
	console.log(arr2) // [2022, 2, 3, Object { a: "你好世界" }, 5]
	// 如果复制的数组是一层,看似是深拷贝;但其实改方法是浅拷贝,复制多层就会发现。

Array.prototype.slice()

slice()方法返回一个新的数组对象,这一对象是一个由 begin 和 end 决定的原数组的浅拷贝(包括 begin,不包括end)。原始数组不会被改变。

	const arr1 = [1,2,3, {a: 4},5,6]
	const arr2 = arr1.slice([1])
	
	arr2[0] = 2022
	arr2[2].a = '你好世界'
	console.log(arr1) // [1, 2, 3, Object { a: "你好世界" }, 5, 6]
	console.log(arr2) // [2022, 3, Object { a: "你好世界" }, 5, 6]

扩展运算符

	const arr1 = [1,2,3, {a: 4},5,6]
	const arr2 = [...arr1]
	
    arr2[0] = 2022
	arr2[3].a = '你好世界'
	console.log(arr1) // [1, 2, 3, Object { a: "你好世界" }, 5, 6]
	console.log(arr2) // [2022, 2, 3, Object { a: "你好世界" }, 5, 6]

☆手写浅拷贝函数

	// 手写浅拷贝
    function shallowCopy(obj1) {
      let obj2 = Array.isArray(obj1) ? [] : {};
      for(let i in obj1) {
        obj2[i] = obj1[i]
      }
      return obj2
    }

    const obj1 = {
      name: '张三',
      age: 20,
      list: [{item: '中国'},1,2,3,4]
    }

    const obj2 = shallowCopy(obj1)
    obj2.name = '李四'
    obj2.list[0].item = '祖国万岁'

    console.log(obj1, 'obj1')
    console.log(obj2, 'obj2')

深拷贝

  • 常见的深拷贝
    • JSON.stringify()
    • ☆手写深拷贝(递归函数)

JSON.stringify()

原理:用JSON.stringify()将对象转成字符串,再用JSON.parse()把字符串解析成对象。

   var obj1 = {
	  name: '张三',
	  list : [{item: '中国'},1,2,3,4]
    };
    var obj2 = JSON.parse(JSON.stringify(obj1));
    obj2.name = "李四";
    obj2.list[0].item = '祖国万岁'
    
    console.log('obj1',obj1)
    console.log('obj2',obj2)

☆手写深拷贝函数

  • 基础版
		function deepCopy(obj){
		 var result= Array.isArray(obj) ? [] : {}
		 if (obj && typeof(obj) === 'object') {
		  for (let i in obj) {
		   if (obj.hasOwnProperty(i)){ // 思考:这句是否有必要?
		    if (obj[i] && typeof(obj[i]) === 'object') {
		     result[i] = deepCopy(obj[i])
		    } else {
		     result[i] = obj[i]
		    }
		   }
		  }
		 }
		 return result
		}
		var obj1 = {
		 a: 1,
		 b: {
		  c: 2
		 }
		};
		var obj2 = deepCopy(obj1);
		obj2.a = '一';
		obj2.b.c = '二'
		console.log('obj1', obj1)
		console.log('obj2', obj2)
  • 进阶版(防止循环递归爆栈)
	function deepCopy(obj, parent = null) {
	 let result = Array.isArray(obj) ? [] : {}
	 let _parent = parent
	 // 该字段有父级则需要追溯该字段的父级
	 while(_parent) {
	  // 如果该字段引用了它的父级,则为循环引用
	  if (_parent.originalParent === obj) {
	   // 循环引用返回同级的新对象
	   return _parent.currentParent 
	  }
	  _parent = _parent.parent
	 }
	 if (obj && typeof(obj) === 'object') {
	  for (let i in obj) {
	   // 如果字段的值也是一个对象
	   if (obj[i] && typeof(obj[i]) === 'object') {
	    // 递归执行深拷,将同级的待拷贝对象传递给parent,方便追溯循环引用
	    result[i] = deepCopy(obj[i], {
	     originalParent: obj,
	     currentParent: result,
	     parent: parent
	    })
	   } else {
	    result[i] = obj[i]
	   }
	  }
	 }
	 return result
	}
	var obj1 = {
	 x: 1,
	 y: 2
	};
	obj1.z = obj1
	var obj2 = deepCopy(obj1)
	console.log('obj1', obj1)
	console.log('obj2', obj2)
  • 终极版(支持基本数据类型、Array、Object、原型链、RegExp、Date类型)
	function deepCopy(obj, parent = null) {
	 let result
	 let _parent = parent
	 while(_parent) {
	  if (_parent.originalParent === obj) {
	   return _parent.currentParent
	  }
	  _parent = _parent.parent
	 }
	 if (obj && typeof(obj) === 'object') {
	  if (obj instanceof RegExp) {
	   result = new RegExp(obj.source, obj.flags)
	  } else if (obj instanceof Date) {
	   result = new Date(obj.getTime())
	  } else {
	   if (obj instanceof Array) {
	    result = []
	   } else {
	    let proto = Object.getPrototypeOf(obj)
	    result = Object.create(proto)
	   }
	   for (let i in obj) {
	    if(obj[i] && typeof(obj[i]) === 'object') {
	     result[i] = deepCopy(obj[i], {
	      originalParent: obj,
	      currentParent: result,
	      parent: parent
	     })
	    } else {
	     result[i] = obj[i]
	    }
	   }
	  }
	 } else {
	  return obj
	 }
	 return result
	}
	var obj1 = {
	 x: 1 
	}
	
	//试调用
	function construct(){
	  this.a = 1,
	  this.b = {
	    x:2,
	    y:3,
	    z:[4,5,[6]]
	  },
	  this.c = [7,8,[9,10]],
	  this.d = new Date(),
	  this.e = /abc/ig,
	  this.f = function(a,b){
	    return a+b
	  },
	  this.g = null,
	  this.h = undefined,
	  this.i = "hello",
	  this.j = Symbol("foo")
	}
	construct.prototype.str = "I'm prototype"
	var obj1 = new construct()
	obj1.k = obj1
	obj2 = deepCopy(obj1)
	
	obj2.b.x = 999
	obj2.c[0] = 666
	
	console.log('obj1', obj1)
	console.log('obj2', obj2)

其他方法

_.cloneDeep()

使用lodash_.cloneDeep()方法,可以实现深克隆。

	const _ = require('lodash');
	let oldObj = {
	    a: 1, b: 2,c:{value:3}
	}
	let newObj = _.cloneDeep(oldObj)
	newObj.a = 2
	newObj.c.value = 4
	console.log(oldObj) // { a: 1, b: 2, c: { value: 3 } }
	console.log(newObj) // { a: 2, b: 2, c: { value: 4 } }

$.extend()

jQuery提供的方法:$.extend()

	let a=[0,1,[2,3],4],
	    b=$.extend(true,[],a);
	a[0]=1;
	a[2][0]=1;
	console.log(a,b);

参考资料

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值