佳宸学习和分享笔记的地方

0%

Js深浅拷贝

JS深浅拷贝

要想深入的确实个值得探究的问题

首先明确两个概念

  • 基本数据类型:String、Number、Boolean、Null、Undefined、Symbol。基本数据类型是直接存储在栈中的数据。
  • 引用数据类型:Array、Object。引用数据类型在该对象在栈中存储的是引用地址,指向内存堆中真实的数据。

所以说如果是引用类型数据的新赋值,会影响到原数据。

深拷贝与浅拷贝的关系

  • 浅拷贝:一层拷贝。修改子引用数据类型会影响原有的数据类型。
  • 深拷贝:无限层级拷贝。修改不会影响原有的数据类型。因为指针指向的堆是新开辟的。

实现浅拷贝

Object.assign

Object.assign() 方法用于将所有可枚举属性的值从一个或多个源对象复制到目标对象。它将返回目标对象。MDN

1
2
3
4
5
6
7
8
9
10
11
12
13
14
const obj = { 
a: 1,
b: { c: 0},
d: [0, 1, 2, 3, 4]
};
//(target,...source) 把obj对象赋到空对象 在给copy变量
const copy = Object.assign({}, obj);
console.log(copy);
obj.a = 2;
copy.a = 3;
copy.d = [4, 3, 2, 1, 0];
copy.b.c = 4;
console.log(obj);
console.log(copy);

注意第一层引用类型数据是深拷贝,对于第二层及以上的引用类型数据来说,是浅拷贝

image-20200108123043489

hasOwnProperty

对象的浅拷贝

hasOwnProperty() 方法会返回一个布尔值,指示对象自身属性中是否具有指定的属性(也就是,是否有指定的键)

1
2
3
4
5
6
7
8
9
function cloneShallow(source) {
var target = {};
for (var key in source) {
if (Object.prototype.hasOwnProperty.call(source, key)) {
target[key] = source[key];
}
}
return target;
}

Array.prototype.concat

concat() 方法用于合并两个或多个数组。此方法不会更改现有数组,而是返回一个新数组。MDN

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// concat 方法例子
const array1 = ['a', 'b', 'c'];
const array2 = ['d', 'e', 'f'];
const array3 = array1.concat(array2);

console.log(array3);
// expected output: Array ["a", "b", "c", "d", "e", "f"]


// 浅拷贝一个数组
// 直接执行函数空参数,然后赋值给变量就是浅拷贝
const array = ['a', 'b', {c: 'deep C'}]
const copyArr = array.concat()
copyArr[0] = 'change A'
copyArr[2].c = 'change Deep C'

console.log(array)
// array: [ 'a', 'b', { c: 'change Deep C' } ] 引用类型数据源被改变
console.log(copyArr)
// copyArr: [ 'change A', 'b', { c: 'change Deep C' } ]

image-20200108210018080

Array.prototype.clice

slice() 方法返回一个新的数组对象,这一对象是一个由 beginend 决定的原数组的浅拷贝(包括 begin,不包括end)。原始数组不会被改变。

1
2
3
4
5
6
7
8
9
10
const animals = ['ant', 'bison', 'camel', 'duck', 'elephant'];

console.log(animals.slice(2));
// expected output: Array ["camel", "duck", "elephant"]

console.log(animals.slice(2, 4));
// expected output: Array ["camel", "duck"]

// 所以说slice传空参数就是浅拷贝
animals.clice()

…展开运算符

此法最简单实用ES6

const objCopy = {...source}

深拷贝

手写深拷贝,通过判断数组和对象然后 递归+浅拷贝实现

解决死循环

这样子循环嵌套,就会进入死循环,使用递归拷贝不会停止

1
2
3
const obj1 = {};
obj1.a = obj1;
console.log(deepClone(obj1));

设置hash表,避免出现重复嵌套的死循环。

如果hash表中由此值时,直接退出

WeakMap 对象是一组键/值对的集合,其中的键是弱引用的。其键必须是对象,而值可以是任意的。MDN

WeakMap.prototype.set()


解决symbol类型

1.getOwnPropertySymbols

1
2
3
4
5
6
7
8
9
10
let symKeys = Object.getOwnPropertySymbols(source); // 查找
if (symKeys.length) { // 查找成功
symKeys.forEach(symKey => {
if (isObject(source[symKey])) {
target[symKey] = cloneDeep(source[symKey], hash);
} else {
target[symKey] = source[symKey];
}
});
}

2.Reflect.ownKeys()

使用这个方法比较简单,MDN返回一个由目标对象自身的属性组成的数组

可以直接返回symbol的键名

可以通过 toString() 来获取每个对象的类型。为了每个对象都能通过 Object.prototype.toString() 来检测,需要以 Function.prototype.call() 或者 Function.prototype.apply() 的形式来调用,传递要检查的对象作为第一个参数,称为 thisArg

1
2
3
4
5
6
7
8
9
10
> var toString = Object.prototype.toString;
>
> toString.call(new Date); // [object Date]
> toString.call(new String); // [object String]
> toString.call(Math); // [object Math]
>
> //Since JavaScript 1.8.5
> toString.call(undefined); // [object Undefined]
> toString.call(null); // [object Null]
>

Object.prototype.toString().slice(8,-1)截取掉前面的[object 和 ] 就可以获得数据类型了

简单实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
function isObject(obj) {
return typeof obj === 'object' && obj !== null;
}
function deepClone (source, hash = new WeakMap()) {
if (!isObject(source)) return source
// 如果数据存在 hash表里 函数直接退出返回 hash中的数据 避免死循环
if (hash.has(source)) {
// 返回key关联对象, 或者 undefined(没有key关联对象时)。
return hash.get(source)
}// 查哈希表
// 判断是对象还是数组
let target = Array.isArray(source) ? [...obj] : {...obj}
hash.set(source, target) // 哈希表设值

Reflect.ownKeys(newObj).forEach(key => {
newObj[key] = isObject(obj[key]) ? deepClone(obj[key], hash) : obj[key]
})
return target
}

解决递归爆栈

简单实现, 各种边界类型还没实现,只能是对象

具体思路: 定义一个树的数据结构:有parent父节点,key键名,data键值。循环遍历树的方式:使用栈,开始循环,从栈中取出一个节点,如果是引用类型,推入栈,如果是普通类型直接赋值。直到栈中没有数据,说明全都遍历完了,可以单步调试去看看效果。因为是递归,所以是反着来的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
function cloneLoop(x) {
const root = {};

// 栈
const loopList = [
{
parent: root,
key: undefined,
data: x,
}
];

while(loopList.length) {
const node = loopList.pop();
const parent = node.parent;
const key = node.key;
const data = node.data;

// 初始化赋值目标,key为undefined则拷贝到父元素,否则拷贝到子元素
let res = parent;
if (typeof key !== 'undefined') {
res = parent[key] = {};
}

for(let k in data) {
if (data.hasOwnProperty(k)) {
if (typeof data[k] === 'object' && data[k] !== null) {
// 下一次循环
loopList.push({
parent: res,
key: k,
data: data[k],
});
} else {
res[k] = data[k];
}
}
}
}

return root;
}