js 浅拷贝与深拷贝原理解析即常用实现

数据类型的储存

要说浅拷贝与深拷贝,就首先得从数据类型说起,众所周知,js的数据类型大体分为两类,基本数据类型和引用数据类型,基本数据类型包括:Number,String,boolean,null,undefined;引用数据类型包括function,Object和Array。其中基本数据类型储存在栈内存中,引用数据类型储存在堆内存中,这里详细的可以借鉴JavaScript变量——栈内存or堆内存这篇博文。而我们需要了解的是,当变量赋值时,传递的值究竟是什么?

赋值传递

let a = 1
let b = a
a = 2		// a = 2 b = 1
let str1 = '1'
let str2 = str1
str1 = '2'	// str1: '2' str2: '1'
let obj1 = {name: '张三'}
let obj2 = obj1
obj1.name = '李四'	//obj1:{name: '李四'} obj2:{name: '李四'}

这里可以发现,对于基本数据类型来说,将a赋值给b,a的值改变后,b的值没有改变,传递的值便是栈内存中存储的该数据类型本身的值;但对于引用数据类型来说,将obj1赋值给obj2,obj1的值改变,obj2的值也改变,是因为在obj1创建之时,会为obj1同时分配栈内存与堆内存,堆内存中存储的是obj1各个属性的值,而栈内存中存储的则是该堆内存的地址。将obj1赋值给obj2时,传递的值只是栈内存中的地址值。obj1与obj2指向同一个堆内存地址,因此当修改obj1的堆内存值时,由于栈内存中的地址值并没有发生变化,仍指向这片堆内存,所以查看堆内存时,obj1与obj2的指向的name同时发生了变化。

浅拷贝与深拷贝

结合上面的例子,其实浅拷贝就是分配出一个新的堆内存来存储该对象中的属性值,但只关心该对象中的栈内存的值(包括该对象中的对象的地址值)。而深拷贝则是分配一个堆内存来存储对象中的属性值,并为属性中的引用类型分配新的堆内存空间储存。区别:浅拷贝基本类型之前互不影响,引用类型其中一个对象改变了地址,就会影响另一个对象;深拷贝改变新对象不会影响原对象,他们之前互不影响。

实现浅拷贝

方法1:Object.assign()

Object.assign()是ES6中对象的拷贝方法,接受的第一个参数是目标对象,其余参数是源对象,用法:Object.assign(target, source_1, ···),该方法可以实现浅拷贝,也可以实现一维对象的深拷贝。其实该方法类似于数组方法的concat()方法。

let obj = {}				// 目标对象
let obj1 = {a: 1}			//待拷贝对象
Object.assign(obj,obj1)		// obj: {a: 1}

需要注意的是:(1)若目标对象与待拷贝对象上有同名属性,待拷贝对象上的同名属性会覆盖目标对象上的同名属性。(2)若函数只有一个参数,当该参数为对象时,则会直接返回该对象。若不为对象则会转化为对象再返回。(3)由于undefined与null无法转化为对象,因此第一个参数不能为null或undefined。

方法2:…扩展运算符

使用方法:let clone = {...obj},例如:

let obj = {a:1,b:{m:1}}	
let clone = {...obj}					// clone: {a:1,b:{m: 1}}
obj.a = 3								
console.log(obj)						// {a:3, b: {m: 1}}
console.log(clone)						// {a:1, b: {m: 1}}
obj.b.m = 2
console.log(obj)						// {a: 3, b:{m: 2}}
console.log(clone)						// {a: 1, b:{m: 2}}

方法3:Array.prototype.slice()(数组类型对象)

slice(start,end)方法可以截取数组中[start,end)的部分数组,若不携带参数,便可以实现数组的浅拷贝,该方法不会修改原数组的值。

let arr = [1,2,3,4]
let clone = arr.slice()	
arr[0] = 3					
console.log(arr)			// [3,2,3,4]
console.log(clone)			// [1,2,3,4]

方法4:Array.prototype.concat()(数组类型对象)

concat(arr1,arr2,…)方法可以连接多个数组,并返回一个新的数组,不会改变原数组的值。若不携带参数,便可以实现数组的浅拷贝。

let arr = [1,2,3]
let clone = arr.concat()
arr[0] = 3
console.log(arr)					// [3,2,3]
console.log(clone)					// [1,2,3]

手写浅拷贝

function shallowCopy(obj) {
	// 判断obj数据类型
	if(!obj || typeof obj !== 'object') return
	// 判断具体数据类型 考虑返回的是数组还是对象
	let newObj = Array.isArray(obj) ? [] : {}
	// 遍历对象,判断是否是obj的属性,并选择是否copy
	for(let key in obj) {
		if(obj.hasOwnProperty(key)) {
			newObj[key] = obj[key]
		}
	}
	return newObj
}

实现深拷贝

方法一:JSON.stringify()

最常用的方法便是JSON.stringify(),原理是将对象转化为JSON字符串序列,再利用JSON.parse()来还原js对象

let obj = {a:1, b: {m: 1}}
let clone = JSON.parse(JSON.stringify(obj))
obj.b.m = 2
console.log(obj)					// {a: 1, b: {m: 2}}
console.log(clone)					// {a: 1, b: {m: 1}}

需要注意的是,这种暴力深拷贝的方法也有缺陷,当对象中含有undefined,函数,symbol时,使用JSON.stringify()后都会消失:

let obj = {a: 1, b: undefined}
let clone = JSON.parse(JSON.stringify(obj))
console.log(clone)					// {a: 1}

方法2: loadash的_.cloneDeep()

其实很多库中都含有类似的方法,例如jQuery的$.extend(),这里只介绍loadash中的:

var _ = require('loadash')
var obj = {a:1, b: {m: 1}}
var clone = _.cloneDeep(obj)
console.log(obj === clone)				// false

手写深拷贝

function deepCopy(obj) {
	// 判断obj数据类型
	if(!obj || typeof obj !== 'object') return
	// 判断具体数据类型 考虑返回的是数组还是对象
	let newObj = Array.isArray(obj) ? [] : {}
	// 遍历对象,判断是否是obj的属性,并选择是否copy
	for(let key in obj) {
		if(obj.hasOwnProperty(key)) {
			newObj[key] = 
				typeof obj[key] === 'object' ? deepCopy(obj[key]) : obj[key]
		}
	}
	return newObj
}
  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值