深拷贝和浅拷贝

    |     2022年4月23日   |   html/css/js, web前端技术   |     0 条评论   |    1166

深拷贝和浅拷贝是面试高频题,考的是你对基本类型 和 引用类型 在内存里怎么放的理解。 拷贝的目标很明确:从 obj 得到一个「看起来一样」的新对象,但两层引用到底断没断,差别巨大。

内存可以粗分为栈和堆。基本类型的值和指向堆的引用变量放在栈上;对象、数组这类复杂数据放在堆上。浅拷贝只复制最外一层,深拷贝会连嵌套对象一起做成全新副本。

一、先看这一份对象

const obj = {
	name: 'jack',
	age: 18,
	fun: { swiming: '游泳' }, // Object
}

浅拷贝只复制一层,属性值若仍是对象,两边会指向同一块堆内存:

20220423193220

深拷贝则连嵌套对象一起新建:

20220423193605

二、常见实现和各自的坑

1. JSON.parse(JSON.stringify(obj))

函数和 undefined 拷不过去,会丢:

const obj = {
	name: 'jack',
	age: 18,
	hobby: {swiming:'游泳'},
	arr: [{ score: 98 }],
	say: () => {},         // Function    无法拷贝
	number: undefined,     // undefined   无法拷贝
}

2. Object.assign 或展开运算符

Object.assign / {...obj} 只拷一层。嵌套的 Object、Array 仍然共享引用:

const obj = {
	name: 'jack',
	age: 18,
	hobby: {swiming:'游泳'}, // Object   无法拷贝
	arr: [{ score: 98 }],   // Array     无法拷贝
	say: () => {},         // Function   无法拷贝
	number: undefined,     // undefined
}

3. 递归 cloneDeep()

遇到对象(含数组)就再进一层,否则直接赋值。这是下面案例里的主方案。

4. 怎么选

  • 属性值里没有嵌套对象,只用一层:用展开运算符 {...obj};
  • 没有 Function、也没有 undefined:可用 JSON.parse(JSON.stringify(obj));
  • 否则走递归。
方法 深度 丢什么
{...obj} / Object.assign 一层 嵌套对象/数组仍共享
JSON.parse(JSON.stringify) 多层 Function、undefined、循环引用等
递归 cloneDeep 多层 实现时要自己处理循环引用、特殊类型

三、案例

const testJSON = () => {
	const obj = {
		name: 'jack',
		age: 18,
		hobby: { swiming: '游泳' },
		arr: [{ score: 98 }],
		say: () => {},
		number: undefined,
	}
	const cloneObj = JSON.parse(JSON.stringify(obj))
	console.log('obj :', obj, '\n cloneObj :', cloneObj)
}
const testAssign = () => {
	const obj = {
		name: 'jack',
		age: 18,
		hobby: { swiming: '游泳' },
		arr: [{ score: 98 }],
		say: () => {console.log('say>>')},
		number: undefined,
	}
	const cloneObj = { ...obj }
	cloneObj.name = 'rose'
	cloneObj.age = 20
	cloneObj.hobby.swiming = '不会游泳' // cloneObj.hobby == obj.hobby
	cloneObj.arr[0].score = 100
	cloneObj.say()
	console.log('obj :', obj, '\n cloneObj :', cloneObj)
}
const cloneDeep = data => {
	const newData = Array.isArray(data) ? [] : {}
	for (let key in data) {
		if (data[key] && typeof data[key] === 'object') {
			newData[key] = cloneDeep(data[key])
		} else {
			newData[key] = data[key]
		}
	}
	return newData
}
const testCloneDeep = () => {
	const obj = {
		name: 'jack',
		age: 18,
		hobby: { swiming: '游泳' },
		arr: [{ score: 98 }],
		say: () => {},
		number: undefined,
		students: [{ student: { num: { id: 1 } } }],
	}
	const cloneObj = cloneDeep(obj)
	cloneObj.name = 'rose'
	cloneObj.age = 20
	cloneObj.hobby.swiming = '不会游泳'
	cloneObj.arr[0].score = 100
	cloneObj.students[0].student.num.id = 100
	console.log('obj :', obj, '\n cloneObj :', cloneObj)
	console.log(cloneObj.students[0].student.num.id === obj.students[0].student.num.id)
}

四、按类型走的优化封装

const isObjectValue = data => {
	for (let key in data) {
		if (data[key] && typeof data[key] === 'object') {
			if (Object.prototype.toString.call(data[key]) !== '[object Function]') {
				return true
			}
		}
	}
}

const isFunctionOrUndefined = data => {
	for (let key in data) {
		if (data[key] === undefined) {
			return true
		} else if (data[key] && Object.prototype.toString.call(data[key]) === '[object Function]') {
			return true
		} else if (data[key] && typeof data[key] === 'object') {
			if (isFunctionOrUndefined(data[key])) return true
		}
	}
}

const cloneDeepObj = obj => {
	if (obj === undefined) {
		throw new TypeError('param is not undefined')
	}
	if (!isObjectValue(obj)) {
		return { ...obj }
	}
	if (!isFunctionOrUndefined(obj)) {
		return JSON.parse(JSON.stringify(obj))
	}
	return cloneDeep(obj)
}

const testCloneDeepObj = () => {
	const obj = {
		name: 'jack',
		age: 18,
		hobby: { swiming: '游泳' },
		arr: [{ score: 98 }],
		say: function(){console.log(this.name)},
		number: undefined,
	}
	const cloneObj = cloneDeepObj(obj)
	cloneObj.name = 'rose'
	cloneObj.age = 20
	cloneObj.hobby.swiming = '不会游泳'
	cloneObj.arr[0].score = 100
	console.log('obj :', obj, '\n cloneObj :', cloneObj)
	cloneObj.say()
	obj.say()
}

testCloneDeepObj()

原文 isFunctionOrUndefined 递归时没有 return,嵌套的 Function / undefined 会查漏;上面补上了返回值。

一句话总结:浅拷贝只断最外一层引用,深拷贝要连堆里的嵌套对象一起新建;JSON 会丢函数和 undefined,一层展开不够就递归。

转载请注明来源:深拷贝和浅拷贝
本文链接地址:https://ai.zhousir.top/?p=3319

上一篇:

下一篇:

回复 取消