深拷贝
- 浅拷贝:创建一个新对象,这个对象有着原始对象属性值的一份精确拷贝。如果属性是基本类型,拷贝的就是基本类型的值,如果属性是引用类型,拷贝的就是内存地址 ,所以如果其中一个对象改变了这个地址,就会影响到另一个对象。
深拷贝:将一个对象从内存中完整的拷贝一份出来,从堆内存中开辟一个新的区域存放新对象,且修改新对象不会影响原对象
const deepClone = (obj = {}) => {
if (typeof obj !== 'object' || obj == null ) {
return obj
}
// 或者 Array.isArray(target) ? [] : {};
let result = obj instanceof Array ? [] : {}
for (let key in obj) {
if(obj.hasOwnProperty(key)) {
result[key] = deepClone(obj[key])
}
}
return result
}
const res = deepClone(data)
console.log('res', res)
存在问题-循环引用
循环引用,造成递归进入死循环导致栈内存溢出了 ```javascript const obj = { name: ‘zxc’, age: 18 } obj.obj = obj deepClone(obj)
![image.png](https://cdn.nlark.com/yuque/0/2021/png/12492651/1623057478786-bd5f3e01-4f09-4ad9-be53-b21ba2399a14.png#clientId=u45049cda-0605-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=107&id=u05711154&margin=%5Bobject%20Object%5D&name=image.png&originHeight=214&originWidth=908&originalType=binary&ratio=2&rotation=0&showTitle=false&size=96818&status=done&style=none&taskId=uc44a58b2-bd4c-4649-b493-02c8c03fa0e&title=&width=454)
<a name="VfVQz"></a>
#### 解决
我们可以额外开辟一个存储空间,来存储当前对象和拷贝对象的对应关系,当需要拷贝当前对象时,先去存储空间中找,有没有拷贝过这个对象,如果有的话直接返回,如果没有的话继续拷贝,这样就巧妙化解的循环引用的问题<br /> 这个存储空间,需要可以存储key-value形式的数据,且key可以是一个引用类型,我们可以选择Map这种数据结构:
- 检查map中有无克隆过的对象
- 有 - 直接返回
- 没有 - 将当前对象作为key,克隆对象作为value进行存储
- 继续克隆
```javascript
const deepClone = (obj = {}, map = new Map()) => {
if (typeof obj !== 'object' || obj == null ) {
return obj
}
// 或者 Array.isArray(target) ? [] : {};
let result = obj instanceof Array ? [] : {}
if (map.get(obj)) {
console.log('缓存中获取', obj);
return map.get(obj);
}
// 反正是对象的引用,先将对象 set 进去,
// 后面再实现对象属性赋值 result[key] = deepClone(obj[key], map)
// 依然能得到 赋值后的对象
map.set(obj, result);
for (let key in obj) {
if(obj.hasOwnProperty(key)) {
result[key] = deepClone(obj[key], map)
}
}
return result
}
运算符 == 与 ===
类型判断instranceof
原型链
weakMap
- WeakMap结构与Map结构类似,也是用于生成键值对的集合
- WeakMap只接受对象作为键名(null除外),不接受其他类型的值作为键名。
- WeakMap的键名所指向的对象,不计入垃圾回收机制。
- 也就是说,一旦不再需要,WeakMap 里面的键名对象和所对应的键值对会自动消失,不用手动删除引用
使用
const wm = new WeakMap();
const element = document.getElementById('example');
wm.set(element, 'some information');
wm.get(element) // "some information"
// 对比Map对比使用
// 如果我们在全局环境下定义一个对象
const obj = { name: 'zxc', age: 18 }
// 要想v8 回收 obj 这块使用内存,必须 obj = null
obj = null
//上面这样的写法显然很不方便。一旦忘了写obj = null,就会造成内存泄露。
作用
-
Map
是键值对的集合,但是“键”的范围不限于字符串,各种类型的值(包括对象)都可以当作键
- Map 结构提供了“值—值”的对应,是一种更完善的 Hash 结构实现 ```javascript var m = new Map([[‘Michael’, 95], [‘Bob’, 75], [‘Tracy’, 85]]); m.get(‘Michael’); // 95
Map构造函数接受数组作为参数,实际上执行的是下面的算法
```javascript
const items = [
['name', '张三'],
['title', 'Author']
];
const map = new Map();
items.forEach(
([key, value]) => map.set(key, value)
);
初始化Map
需要一个二维数组,或者直接初始化一个空Map
。Map
具有以下方法:
var m = new Map(); // 空Map
m.set('Adam', 67); // 添加新的key-value
m.set('Bob', 59);
m.has('Adam'); // 是否存在key 'Adam': true
m.get('Adam'); // 67
m.delete('Adam'); // 删除key 'Adam'
m.get('Adam'); // undefined
set
Set
和Map
类似,也是一组key的集合,但不存储value。由于key不能重复,所以,在Set
中,没有重复的key要创建一个
Set
,需要提供一个Array
作为输入,或者直接创建一个空Set
var s1 = new Set(); // 空Set
var s2 = new Set([1, 2, 3]); // 含1, 2, 3
扩展运算符和 Set 结构相结合,就可以去除数组的重复成员。
let arr = [3, 5, 2, 2, 5, 5];
let unique = [...new Set(arr)];
// [3, 5, 2]
对象的浅比较与深比较
浅比较
浅比较
也称引用相等,在javascript
中, ===
是作浅比较
,只检查左右两边是否是同一个对象的引用
深比较
深比较
也称原值相等,深比较是指检查两个对象
的所有属性
是否都相等
,深比较
需要以递归的方式遍历两个对象的所有属性,操作比较耗时,深比较
不管这两个对象是不是同一对象的引用。
Object.assign()
参考
注意
同名属性的替换
对于这种嵌套的对象,一旦遇到同名属性,Object.assign
的处理方法是替换,而不是添加。
上面代码中,target
对象的a
属性被source
对象的a
属性整个替换掉了,而不会得到{ a: { b: 'hello', d: 'e' } }
的结果。这通常不是开发者想要的,需要特别小心。
一些函数库提供 **Object.assign**
的定制版本(比如 Lodash 的**_.defaultsDeep**
方法),可以得到深拷贝的合并。
const target = { a: { b: 'c', d: 'e' } }
const source = { a: { b: 'hello' } }
Object.assign(target, source)
// { a: { b: 'hello' } }