数据类型
基本类型:Undefined、Null、Boolean、Number、String、Symbol
引用类型:Object、Array、Date、RegExp、Function
基本类型是按值访问的,可以操作保存在变量中的实际的值,而引用类型的值是保存在内存中的对象,JavaScript不能直接操作直接访问内存中的位置,也就是不能直接操作对象的内存空间,在操作对象时,实际上操作的是对象的引用,所以,引用类型的值是按引用访问的。
引用类型的数据被重新引用时不会复制数据,而是将指针指向之前的数据地址。
Stack 栈:存放基本类型数据和引用类型数据的指针
Heap 堆:存放被引用的引用类型数据
如果一个值是引用类型的,那么它的存储空间将从堆中分配。由于引用值的大小会改变,所以不能把它放在栈中,否则会降低变量查寻的速度。 相反,放在变量的栈空间中的值是该对象存储在堆中的地址。地址的大小是固定的,所以把它存储在栈中对变量性能无任何负面影响。
shallowClone
常见的浅复制函数
Array.prototype.slice
Array.prototype.concat
Array.copyWithin()
Array.from()
Object.assign()
...
特征:改变新复制后的对象属性,原属性也会随之改变。
let oldObj = { name: 'old' }
let newObj = oldObj
newObj.name = 'new'
console.log(oldObj) // {name: "new"} wtf???
DeepClone
为了避免上面的情况,我们要实现深复制,让新复制的对象独立。
1.序列化
function jsonClone(source) {
return JSON.parse(JSON.stringify(obj))
}
2.第三方库的实现
在这三个常见库中lodash的实现更为完备,花了很多代码来实现ES6引入的新的标准对象,并且对存在环的对象进行针对处理。
上面引用lodash中的_.cloneDeep()
是1.0.0的版本,为了大家可以更好地读懂源码。
各个DeepClone方法的比较
特性 | JSON.parse | jQuery | lodash |
---|---|---|---|
浏览器兼容性 | IE8+ | IE6+ (1.x) & IE9+ (2.x/3.x) | IE6+ (Compatibility) & IE9+ (Modern) |
能够深复制存在环的对象 | 抛出异常 TypeError: Converting circular structure to JSON | 抛出异常 RangeError: Maximum call stack size exceeded | 支持 |
对 Date, RegExp 的深复制支持 | x | x | 支持 |
对 ES6 新引入的标准对象的深复制支持 | x | x | 支持 |
复制数组的属性 | x | x | 仅支持RegExp#exec返回的数组结果 |
复制函数 | x | x | x |
3.自己实现(层层递归,较简易,不完美)
function deepClone(source) {
if (!source && typeof source !== 'object') {
throw new Error('error arguments', 'shallowClone')
}
const targetObj = source.constructor === Array ? [] : {}
for (const keys in source) {
// 遍历一个对象的所有属性时忽略掉继承属性
if (source.hasOwnProperty(keys)) {
if (source[keys] && typeof source[keys] === 'object') {
targetObj[keys] = source[keys].constructor === Array ? [] : {}
// 递归
targetObj[keys] = deepClone(source[keys])
} else {
targetObj[keys] = source[keys]
}
}
}
return targetObj
}
参考资料
《JavaScript高级程序设计》
你做的拷贝是真的深拷贝吗
深入剖析 JavaScript 的深复制