概念介绍
首先来认识三个概念:复制、浅拷贝、深拷贝,还是来个简单的总结。
| 和原始值是否指向同一个对象 | 第一层数据为基本类型 | 第一层数据为引用类型 | |
|---|---|---|---|
| 复制 | 是 | 改变会使原始值异同改变 | 改变会使原始值一同改变 |
| 浅拷贝 | 否 | 改变原始值不发生变化 | 会使原始值一同改变 |
| 深拷贝 | 否 | 改变原始值不发生变化 | 改变原始值不发生变化 |
我们知道,对象的名字是存在栈内存中的,而它的值是存在堆内存中的。这样的话,复制其实就是讲一个对象的地址指向原始值,名义上和原始值是同一个对象。来一个小小的例子:
let obj1 = {
name: 'zhangsan',
age: 35,
children: ['one', 'two']
}
let obj2 = obj1
obj2.name = 'lisi'
obj2.children.push('three')
console.log(obj1, obj2) //{name: "lisi", age: 35, children: Array(3)} {name: "lisi", age: 35, children: Array(3)}
打印后发现,obj1和obj2互相影响,不管它的第一层为基本数据还是引用数据,一个发生变化,另外一个也会发生变化。
至于浅拷贝,根据上面表格里的描述,第一层数据为原始值是相互之间不影响,第一层数据为引用值时相互影响,也就是说将origin对象拷贝到target中,但不包括origin中的子对象,我们来手动实现一个浅拷贝:
/**
* 实现一个浅拷贝
* @param origin 原始对象
* @param target 拷贝后的对象
*/
function clone(origin, target){
let tar = target || {}
for(let key in origin){
if(origin.hasOwnProperty(key)){
tar[key] = origin[key]
}
}
return tar
}
至于深拷贝是什么呢?深拷贝就是讲整个原对象拷贝到一个新的对象中,另开辟一个堆内存放新对象的地址,新旧之间没有任何影响。至于怎么实现深拷贝,不着急,先来一个方法的介绍,等下会用到。
Object.prototype.toString方法
如果需要判断一个数据是什么类型的,你首先想到的方法是什么?typeof? instanceof? typeof 只能实现基本类型的判断,对于引用类型,typeof的结果是一致的,都为'object',这样的话我们并不能判断你的数据是对象还是数组,而且最变态的一点,typeof null 居然等于'object''object'。至于instanceof,是用来判断一个实例是否属于某种类型,并不能确切的帮我们找到它的类型,这个时候Object.prototype.toString就要上场了,它可以帮助检测数据类型。
/**
* 获取数据类型
* @param obj 指定的数据
*/
function getDataType(obj){
let type = Object.prototype.toString.call(obj)
// 可以得到类似这样的一个字符串 [object Object]
let reg = /(\[object\s)(\w+)(\])/g
return type.replace(reg, '$2')
}
调用下这个方法,
getDataType([]) //"Array"
getDataType({}) //"Object"
getDataType(Math) //"Math"
getDataType(new Map()) //"Map"
getDataType(new Set()) //"Set
getDataType(null) //"Null""
...
香不香,啥数据都能检验
实现一个深拷贝
实现深拷贝最简单的方法是通过JSON.parse(JSON.stringify(要拷贝的对象)),不过这种方式会有以下一些弊端:
- 对象有函数或undefined,会造成函数和undefined的丢失
- 对象里有RegExp、Error对象,则序列化的结果将只得到空对象
- 对象里有NaN、Infinity和-Infinity,则序列化的结果会变成null
let originObj = {
name: 'test',
say(){
console.log('123')
},
age: undefined,
date: new RegExp('\\w+')
}
let targetObj = JSON.parse(JSON.stringify(originObj))
targetObj.sex = 'nv'
console.log(originObj, targetObj)
控制台打印originObj, targetObj,可以看到以上列到的一些情况。
/**
* 深拷贝
* @param origin 原对象
* @param target 目标对象
*/
function deepClone(origin, target){
let target = target || {}
for(let key in origin){
if(origin.hasOwnProperty(key)){//只拷贝自有属性
if(typeof origin[key] !== "object"){
target[key] = origin[key]
}else{
target[key] = getDataType(origin[key]) === "Array" ? [], {} //借助上一步的这个方法
deepClone(origin[key], target[key])
}
}
}
return target
}
来测试一下,原对象和拷贝后的对象互不影响,这样我们就实现了一个深拷贝。