【JavaScript】聊一聊js中的浅拷贝与深拷贝与手写实现

javascript,js · 浏览次数 : 32

小编点评

浅拷贝与深拷贝是JavaScript中用于复制对象或数据的方法。它们的主要区别在于对引用类型的处理方式。 浅拷贝:创建一个新的数据结构,但只复制基本数据类型的值,对于引用类型,只是复制了引用地址,因此深拷贝和浅拷贝中的引用类型会共享内存地址。当修改其中一个对象的属性时,另一个对象的该属性也会受到影响。 深拷贝:与浅拷贝不同,深拷贝会开辟一个新的栈,创建一个新的对象,并将原对象的所有属性和值复制到新对象中。这样,即使原对象中的引用类型属性被修改,新对象中的引用类型属性也不会受到影响。 JavaScript中常用的深拷贝方法有`JSON.parse`和`deepClone`函数。`JSON.parse`是将一个JSON字符串转换为一个JavaScript对象,但由于它只会拷贝基本数据类型,所以在处理包含复杂数据结构的对象时可能会遇到问题。`deepClone`函数是一个递归函数,它会遍历对象的属性,并对每个属性进行深拷贝。 以下是一个简单的深拷贝实现: ```javascript function deepClone(obj, hash = new WeakMap()) { if (obj === null) return obj; if (obj instanceof Date) return new Date(obj); if (obj instanceof RegExp) return new RegExp(obj); if (typeof obj !== "object") return obj; if (hash.has(obj)) return hash.get(obj); let cloneObj = new obj.constructor(); hash.set(obj, cloneObj); for (let key in obj) { if (obj.hasOwnProperty(key)) { cloneObj[key] = deepClone(obj[key], hash); } } return cloneObj; } ``` 这段代码首先检查对象是否为`null`或`undefined`,然后根据对象类型分别处理。对于非对象类型,直接返回。接下来,它使用一个`WeakMap`来确保对象的属性不会被重复添加到克隆对象上。最后,它遍历原对象的属性,并对每个属性调用递归的深拷贝函数。

正文

前言

什么是深拷贝与浅拷贝?深拷贝与浅拷贝是js中处理对象或数据复制操作的两种方式。‌在聊深浅拷贝之前咱得了解一下js中的两种数据类型:

基本数据类型(6种)
String、Number、Object、Boolean、null、undefined、symbol(ES6+)

引用数据类型
Object(function、Array、正则表达式等皆是对象)

  • 数据的存储方式是什么?

基本数据: 基本数据类型是存放在栈中的简单数据段,它们是直接按值存放的,所以可以直接按值访问
引用类型: 引用类型是存放在堆内存中的对象,保存的在栈内存中的一个指针,保存的是栈内存中对象在堆内存中的引用地址。通过这个引用地址可以快速查找到保存中堆内存中的对象。

1.浅拷贝

1.1 什么是浅拷贝

浅拷贝,指的是创建新的数据,这个数据有着原始数据属性值的一份精确拷贝。如果属性是基本类型,拷贝的就是基本类型的值。如果属性是引用类型,拷贝的就是内存地址即浅拷贝是拷贝一层,深层次的引用类型则共享内存地址。

  • 下面用一张图来解释一下浅拷贝
    image

1.2 浅拷贝实现方法

1.2.1 assign

var obj = {
	age: 18,
	person: {
		name1: 'fx',
		name2: 'xka'
	},
	list:['hhh','666'],
	love: function () {
		console.log('嘿嘿')
	}
}
var newObj = Object.assign({}, obj);
//因为是浅拷贝,所以只拷贝了基本类型的,引用类型还是共享内存地址的,即改变obj的应用类型的内容,newObj里面的引用类型的值也随之改变
obj.person.name1='xxx'
obj.list[0]='xxx'
console.log(newObj.person.name1) //xxx

1.2.2 slice

const fxArr = ["One", {
	name: "Two",
	age: 20
}, "Three"]
const fxArrs = fxArr.slice(0,)
fxArr[1].name = "four";
console.log(fxArrs[1].name) //four

1.2.3 concat

const fxArr = ["One", {
	name: "Two",
	age: 20
}, "Three"]
const fxArrs = fxArr.concat()
fxArr[1].name = "four";
console.log(fxArrs[1].name) //four

1.2.4 拓展运算符

const fxArr = ["One", {
	name: "Two",
	age: 20
}, "Three"]
const fxArrs = [...fxArr]
fxArr[1].name = "four";
console.log(fxArrs[1].name) //four

2.深拷贝

2.1 什么是深拷贝

深拷贝开辟一个新的栈,两个对象属完成相同,但是对应两个不同的地址,修改一个对象的属性,不会改变另一个对象的属性

  • 下面用一张图来解释一下深拷贝
    image

2.2 浅拷贝实现方法

2.2.1 JSON.parse(常用)

var obj = {
	age: 18,
	person: {
		name1: 'fx',
		name2: 'xka'
	},
	list:['hhh','666'],
	love: function () {
		console.log('嘿嘿')
	}
}

const obj2=JSON.parse(JSON.stringify(obj));
obj.person.name1='6666'
console.log(obj2.person.name1) //fx
  • 我常用的基本就是JSON.parse了,然而其他的,之前听过的lodash的cloneDeep,jq的extend我都没使用过。
  • 但是适用JSON.parse会有一个缺点,就是处理的数据里面有undefined、function、symbol会被忽略,但是这也是一个优点,可以利用其特性将undefined等数据排除,拿到干净的数据。还有一个缺点就是在处理的数据比较大的话,还有性能问题。

3.手写实现深浅拷贝

3.1 浅拷贝

function clone(object){
	const newObj={}
	for(let proto in object){
		if(object.hasOwnProperty(proto)){
			newObj[proto]= object[proto]
		}
	}
	return newObj
}
var obj = {
	age: 18,
	person: {
		name1: 'fx',
		name2: 'xka'
	},
	list:['hhh','666'],
	love: function () {
		console.log('嘿嘿')
	}
}

const obj1=clone(obj)
console.log(obj)
console.log(obj1)

3.2 深拷贝

// 手写深拷贝
function deepClone(obj, hash = new WeakMap()) {
	// 数据过滤
	if (obj === null) return obj; // 如果是null或者undefined我就不进行拷贝操作 
	if (obj instanceof Date) return new Date(obj);// 如果传入的对象是日期对象,使用 new Date() 创建一个新的日期对象并返回
	if (obj instanceof RegExp) return new RegExp(obj);// 如果传入的对象是正则表达式对象,使用 new RegExp() 创建一个新的正则表达式对象并返回
	// 如果传入的对象不是普通对象(即不是对象类型)或普通的值 如果是函数的话就不需要深拷贝
	// 因为拷贝的只需要考虑是否为对象,所以只需要判断obj是否为对象类型即可,因为null或者undefined在上面已经先过滤掉了,此时就只剩下基本数据类型和函数了
	if (typeof obj !== "object") return obj;
	// 来到此处的话就只剩下对象了,就要进行深拷贝
	if (hash.get(obj)) return hash.get(obj);
	// 深拷贝
	// 创建一个新对象,这个新对象和obj对象类型相同
	// 找到的是所属类原型上的constructor属性,而原型上的constructor指向的是当前类本身
	let cloneObj = new obj.constructor();
	hash.set(obj, cloneObj);
	for (let key in obj) {
		if (obj.hasOwnProperty(key)) {
			// 实现一个递归拷贝
			cloneObj[key] = deepClone(obj[key], hash);
		}
	}
	return cloneObj;
}
var obj = {
	age: 18,
	person: {
		name1: 'fx',
		name2: 'xka'
	},
	list:['hhh','666'],
	love: function () {
		console.log('嘿嘿')
	}
}


const obj2 = deepClone(obj) // 深拷贝
const obj3 = Object.assign({}, obj) // 浅拷贝
const obj4 = clone(obj) // 浅拷贝
obj.person.name1 = 'hhh';
//因为是深拷贝,obj2中的引用类型新开辟了一个内存地址,所以obj的person改变obj2不受影响
console.log(obj2.person.name1) //fx
//因为是浅拷贝,obj3、obj4中的引用类型与obj中的引用类型共享内存地址,所以obj的person改变obj3、obj4皆受影响
console.log(obj3.person.name1) //hhh
console.log(obj4.person.name1) //hhh

上述为个人学习整理内容,水平有限,如有错误之处,望各位园友不吝赐教!如果觉得不错,请点击推荐和关注!谢谢~๑•́₃•̀๑ [鲜花][鲜花][鲜花]

与【JavaScript】聊一聊js中的浅拷贝与深拷贝与手写实现相似的内容:

【JavaScript】聊一聊js中的浅拷贝与深拷贝与手写实现

什么是深拷贝与浅拷贝?深拷贝与浅拷贝是js中处理对象或数据复制操作的两种方式。‌在聊深浅拷贝之前咱得了解一下js中的两种数据类型:

JavaScript快速入门(一)

JavaScript快速入门(二) 语句 只需简单地把各条语句放在不同的行上就可以分隔它们 var a = 1 var b = 2 如果想把多条语句放在同一行上,就需要用分号隔开 var a = 1; var b = 2 注释 用两个斜线作为一行的开始,这一行就会被当成一条注释 //记得写注释 多行

第一百一十六篇: JavaScript理解对象

好家伙,本篇为《JS高级程序设计》第八章“对象、类与面向对象编程”学习笔记 1.关于对象 ECMA-262将对象定义为一组属性的无序集合。严格来说,这意味着对象就是一组没有特定顺序的值。 对象的每个属性或方法都由一个名称来标识,这个名称映射到一个值。正因为如此(以及其他还未讨论的原因),可以把 EC

第一百一十七篇: JavaScript 工厂模式和原型模式

好家伙,本篇为《JS高级程序设计》第八章“对象、类与面向对象编程”学习笔记 1.工厂模式 工厂模式是另外一种关注对象创建概念的创建模式。 它的领域中同其它模式的不同之处在于它并没有明确要求我们使用一个构造器。 取而代之,一个工厂能提供一个创建对象的公共接口,我们可以在其中指定我们希望被创建的工厂对象

JavaScript系列:JS实现复制粘贴文字以及图片

目录一. 基于 Clipboard API 复制文字(推荐)基本概念主要方法使用限制实际应用示例二、基于 document.execCommand('copy')缺陷实际应用示例说明三、复制图片功能四、封装 一. 基于 Clipboard API 复制文字(推荐) 基本概念 Clipboard AP

【FAQ】关于JavaScript版本的华为地图服务Map的点击事件与Marker的点击事件存在冲突的解决方案

一. 问题描述 创建地图对象,并添加marker标记,对map和marker均添加了点击事件;

神奇的JavaScript弱等价类型转换

JavaScript语言特性 - 类型转换 JavaScript这门语言的类型系统从来没有它表面看起来的那样和善,虽然比起Java、C#等一众强类型语言,它的弱类型使用起来似乎是如此便利,但正因为它极高的自由度,所以才会衍生出令人摸不着头脑的荒诞行为。 举个例子,虽然我们都知道一个包含内容的字符串会

如何在低代码平台中引用 JavaScript ?

引言 在当今快速发展的数字化时代,企业对业务应用的需求日益复杂且多元。低代码开发平台作为一个创新的解决方案,以直观易用的设计理念,打破了传统的编程壁垒,让非技术人员也能轻松构建功能完备的Web应用程序,无需深入编码。这一特性极大地简化了应用开发流程,加速了业务需求转化为实际应用的速度,为企业带来了前

Node工程的依赖包管理方式

在前端工程化中,JavaScript 依赖包管理是非常重要的一环。依赖包通常是项目所依赖的第三方库、工具和框架等资源,它们能够帮助我们减少重复开发、提高效率并且确保项目可以正确的运行。

字符串— trim()、trimStart() 和 trimEnd()

在今天的教程中,我们将一起来学习JavaScript 字符串trim()、trimStart() 和 trimEnd()。 01、trim() 学习如何使用 JavaScript trim()方法从字符串的两端删除空格字符。 JavaScript trim() 方法介绍 String.prototy