为什么要对js的对象进行拷贝?
var a = { a: 'a' }
var b = a
b.a = 'b'
a.a // 'b'
因为js中的对象(引用数据类型)保存在堆中,而多个变量引用同一块堆中的内容时,其中一个修改,则会影响所有引用改内容的变量;有时候我们不希望这样
实现拷贝有什么办法?
方法一
浅拷贝,对目标对象进行遍历,然后将其属性逐一拷贝至新建对象中
function shallowCopy(source) {
const obj = {}
for (let i in source) {
obj[i] = source[i]
}
return obj
}
测试一下
var obj = {
a: 1,
b: [1, 2, 3],
c: {c1: 4}
}
var obj1 = shallowCopy(obj)
obj1.a = 11
obj1.b[0] = 11
obj1.c.c1 = 44
obj.a // 1
obj.b[0] // 11
obj.c.c1 // 44
测试结果
浅拷贝适用于对象中所有属性都为基础数据类型时,如果源对象包含有引用类型的属性,则浅拷贝无法切断与源对象的关系
es6提供了Object.assign,功能与之类似
还有展开运算符: var obj1 = {...obj}
方法二
针对方法一的问题,提出新的方法:
利用JSON.stringify + JSON.parse 进行对象的序列化/反序列化进行对象的拷贝
var obj1 = JSON.parse(JSON.stringify(obj))
obj1.a = 11
obj1.b[0] = 11
obj1.c.c1 = 44
obj.a // 1
obj.b[0] // 1
obj.c.c1 // 4
方法二似乎完美得解决了方法一遗留的问题,再进行测试
function Person(age) {
this.age = age
}
var p = new Person(3)
var obj = {
a: new Date(),
b: /abc/igmuy,
c() {},
d: new Array(2),
e: p,
}
var obj1 = JSON.parse(JSON.stringify(obj))
obj1.a // '2019-12-18T08:40:21.650Z' 实际上和调用Date对象的toISOString方法一致,返回的为时间字符串
obj1.b // {} RegExp对象不会被正确拷贝
obj1.c // undefined 函数不会被正确拷贝
obj1.d // [null, null] 数组空位不会被正确拷贝
obj1.e // obj1.e.constructor指向Object,即原型丢失
还有一种情况,包含循环引用过的对象使用JSON序列化会抛出异常
var obj = {}
obj.a = obj
var obj1 = JSON.parse(JSON.stringify(obj))
// 结果会抛出异常
使用JSON序列化/反序列化实现拷贝的局限性
- 无法对函数、RegExp、Date等特殊对象进行拷贝
- 拷贝稀疏数组时会拷贝相同长度但以null填充的数组
- 循环引用对象使用该方法会导致异常
- 对对象属性进行拷贝时会丢失对象的原型链
以上几点虽然是该方法的局限,但通常该方法能满足大多数场景,也不失为一个简便的方法
方法三
针对方法一无法对引用类型属性进行拷贝和方法二存在的局限,可以针对不同情况,进行针对性处理
// 类型判断方法
const isType = (source, type) => Object.prototype.toString.call(source).slice(8, -1).toLowerCase() === type.toLowerCase()
const deepClone = source => {
const sources = []
const children = []
const _deep = source => {
if (typeof source !== 'object' ||
isType(source, 'null') ||
isType(source, 'undefined')
) {return source}
let child;
if (isType(source, 'date')) { // 处理date类型
child = new Date(source.getTime())
}
else if (isType(source, 'regexp')) { // 处理正则
child = new RegExp(source.source, source.flags)
child.lastIndex = source.lastIndex
}
else if (isType(source, 'array')) { // 处理数组
child = []
}
else {
const proto = Object.getPrototypeOf(source)
child = Object.create(proto)
}
// 处理循环引用
const index = sources.indexOf(source)
if (index >= 0) {
return children[index]
}
sources.push(source)
children.push(child)
for (let i in source) {
child[i] = _deep(source[i])
}
return child
}
return _deep(source)
}
该方法解决了前两个方法中存在的一些问题
方法四
虽然方法三解决了一些问题,但同时引入了一些问题: 由于使用了遍历递归的方法,在性能方面会有一些问题,有没有办法优化呢?
。。。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。