likes
comments
collection
share

前端小白对于深拷贝和浅拷贝的应用和思考

作者站长头像
站长
· 阅读数 46

前言:很多人无论在项目或者面试都会用到拷贝,而拷贝分为深拷贝,浅拷贝,笔者目前是入行没多久,写下文章一是为了记录自己的成长,二是想分享知识,如有不对,请不吝赐教(* ̄︶ ̄)

浅拷贝

浅拷贝 : 浅拷贝是指对基本类型的值拷贝,以及对对象类型的地址拷贝。它是将数据中所有的数据引用下来,依旧指向同一个存放地址,拷贝之后的数据修改之后,也会影响到原数据的中的对象数据。最简单直接的浅拷贝就是直接赋值,如:let obj = xxx或者Array.prototype.slice()

深拷贝

深拷贝:指复制并创建一个一模一样的对象,不共享内存,修改新对象,旧对象保持不变。

应用场景:常用的场景就是表格编辑对话框回显,你需要深拷贝当前行数据赋值上去,否则当你修改编辑框的数据,你会发现表格的对应行的数据也会跟着修改。

Object.assign()

//用法
let obj = {a:'a',b:'b'}
let newObj = Object.assign({},obj)

用法很简单,可以拷贝复杂类型。但是有限制,首先它只能用于对象的拷贝,其次它是只能深拷贝第一层,第二层开始就是浅拷贝了(一深二浅),不能拷贝循环引用类型。

实验代码:

let obj = {
    date:new Date(),
    regexp:new RegExp(),
    err:new Error(),
    fn:()=>{
        console.log(222)
    },
    un:undefined,
    nan:NaN,
    c:{
        fn:new Date()
    }
}
// obj.a=obj //循环引用类型不能拷贝,报错,大概意思是说要转为json格式
let arr = Object.assign({},obj)
console.log('obj',obj)
console.log('arr',arr)

实验截图:

前端小白对于深拷贝和浅拷贝的应用和思考

拓展运算符

拓展运算符的功能很强大,可以用于对象数组,可以拷贝对象和数组,可以将数组转换为参数序列,复制数组,合并数组等。这里主要讲的拷贝功能,其他功能读者有兴趣可以另行寻找文章了解。

//扩展运算符(…)用于取出参数对象中的所有可遍历属性,拷贝到当前对象之中
let obj = {a:'a',b:'b'}
let arr = [1,2,3]
let newObj = {...obj}
let newArr = [...arr]

用法也是很简单,也可以拷贝复杂类型。但是也有限制。仅作为第一层是为深拷贝,可以拷贝复杂类型,对于数组和对象都一样,对于第二层极其以后的值,扩展运算符将不能对其进行打散扩展,也不能对其进行深拷贝,即拷贝后和拷贝前第二层中的对象或者数组仍然引用的是同一个地址,其中一方改变,另一方也跟着改变。

实验代码:

//这里只贴上对于数组的操作,对象的操作跟上面的差不多,拓展运算符的拷贝也不能拷贝循环引用的数据
let arr = [1,2,3,[2,3,[4]]]
let newArr = [...arr]
console.log('arr',arr)
console.log('newArr',newArr)

实验截图

前端小白对于深拷贝和浅拷贝的应用和思考

JSON.parse(JSON.stringify(obj))

这种方式是我在项目中用的最多的方法。但是局限性比前两个还多。它只适用于纯数据json对象的深度克隆。

//用法
let obj = {a:'a'}
let json = JSON.parse(JSON.stringify(obj))

用法简单,局限性很多,目前我所了解的大概有这些:

  1. 如果obj里面存在时间对象,JSON.parse(JSON.stringify(obj))之后,时间对象变成了字符串。
  2. 如果obj里有RegExp、Error对象,则序列化的结果将只得到空对象。
  3. 如果obj里有函数,undefined,则序列化的结果会把函数, undefined丢失。
  4. 如果obj里有NaN、Infinity和-Infinity,则序列化的结果会变成null。
  5. JSON.stringify()只能序列化对象的可枚举的自有属性。如果obj中的对象是有构造函数生成的, 则使用JSON.parse(JSON.stringify(obj))深拷贝后,会丢弃对象的constructor。
  6. 如果对象中存在循环引用的情况也无法正确实现深拷贝。

实验代码:

//定义一个构造函数
    function Person(age){
        this.age = age
    }
    let obj = {
        date: new Date(),
        regexp: new RegExp(),
        err: new Error(),
        fn: () => {
            console.log(222)
        },
        un: undefined,
        nan: NaN,
        c: {
            fn: new Date()
        },
        age:new Person(20)
    }
    obj.a = obj //无法拷贝循环引用的数据,报错,大概意思是说要转为json格式
    let json = JSON.parse(JSON.stringify(obj))
    console.log('obj', obj)
    console.log('json', json)

实验截图:

前端小白对于深拷贝和浅拷贝的应用和思考

使用较为完整的深拷贝

Lodash

建议使用Lodash库,这是一个很强大的库,里面有各种各样的封装方法,十分强大。

1.安装

 npm i lodash

2.组件引入

import _ from 'lodash'

3.使用

const form = _.cloneDeep(拷贝的对象)

使用JQ

通过jq的$.extend()实现深拷贝,当extend内的第一个参数为true时,实现的是深拷贝,false是浅拷贝。

使用消息通道MessageChannel()

应用场景:

  • 不同浏览器上下文通信。比如window.open()打开的窗口或者iframe等之间建立通信管道,并通过两端的端口(port1和port2)发送消息。因此实现专用信道通信,而不必对非指定窗口进行过滤。
  • worker跨线程通信
  • 对象深拷贝,利用消息在发送和接收的过程需要序列化和反序列化。

这里我们就说明如何使用MessageChannel来进行深拷贝

	let msgObj1 = {
		a: '张三', age: 12,
		date: new Date(),
		regexp: new RegExp(),
		err: new Error(), un: undefined,
		nan: NaN,
		c: {
			fn: new Date()
		},
		[Symbol("z")]: "d"
	}
	// let msgObj1 = { a: '张三', age: 12,fn:()=>{}} //包含函数
	msgObj1.c = msgObj1//循环引用
	let msgObj2 = null
	function deepClonePostMessage(obj) {
		return new Promise((resolve) => {
			try {
				/*这个函数能够创建一个消息通道
			        包含两个端口对象,代表两个端点,两个端点可以相互传递消息
				*/
				const { port1, port2 } = new MessageChannel()
				port1.postMessage(obj)
				port2.onmessage = (msg) => {
					resolve(msg.data)
				}
			} catch (err) {
				console.log(err)
			}
		})
	}
	deepClonePostMessage(msgObj1).then(res => {
		msgObj2 = res
		console.log(msgObj1)
		console.log(res)
		console.log(msgObj1 === msgObj2)//false,说明对象是不一样的
	})

前端小白对于深拷贝和浅拷贝的应用和思考

前端小白对于深拷贝和浅拷贝的应用和思考

由上面的截图可知使用MessageChannel进行深拷贝,循环引用,正则,日期,错误undefined等都没问题。 Symbol类型则无法拷贝,函数类型则会报错。

自定义方法

这里的方法是从网上搬运过来的,我之前面试的时候就背这个,个人觉得挺全面的

function deepClone(obj, cache = new WeakMap()) {
	if (typeof obj !== 'object') return obj //普通类型,直接返回
	if (obj === null) return obj
	if (cache.get(obj)) return cache.get(obj)//防止循环引用,程序进入死循环
	if (obj instanceof Date) return new Date(obj)//返回时间格式
	if (obj instanceof RegExp) return new RegExp(obj)//返回正则
	if (typeof obj === 'symbol') return Symbol(obj.description)	// 处理 Symbol
	//找到所属原型上的constructor,所属原型上的constructor指向点前对象的构造函数
	let cloneObj = new obj.constructor()
	// console.log(cloneObj)
	cache.set(obj, cloneObj)//缓存拷贝的对象,用于处理循环引用的情况
		for (let key in obj) {
		//hasOwnProperty() 方法会返回一个布尔值,指示对象自身属性中是否具有指定的属性(也就是,是否有指定的键)。
			if (obj.hasOwnProperty(key)) {
				// console.log(key)
					cloneObj[key] = deepClone(obj[key], cache)//递归拷贝
			}
		}
		return cloneObj
	}

	//测试
	const obj = { name: 'Jack', address: { x: 100, y: 200 }, a: [1, 2, 3, 4], b: Symbol('22') }
	obj.a = obj//循环引用,会一直嵌套
	const newObj = deepClone(obj)
	console.log('obj', obj)
	console.log('newObj', newObj)

总结:学艺不精,请多多担待,写下这篇文章主要是为了记录自己的成长,如果能顺便帮到你那笔者会很开心。如果有大佬有宝贵的意见可以指导,将万分感谢(* ̄︶ ̄)!

转载自:https://juejin.cn/post/7168383396370972680
评论
请登录