深拷贝与浅拷贝
说明
深拷贝与浅拷贝笔记总结、原理、实现
# 浅拷贝原理及实现
创建新的数据,这个数据有着原始数据属性值的一份精确拷贝
如果属性是基本类型,拷贝的就是基本类型的值。如果属性是引用类型,拷贝的就是内存地址
即浅拷贝是拷贝一层,深层次的引用类型则共享内存地址
Object.assign
Array.prototype.slice()
,Array.prototype.concat()
- 使用拓展运算符实现的复制
function shallowClone(obj) {
const newObj = {};
for(let prop in obj) {
if(obj.hasOwnProperty(prop)){
newObj[prop] = obj[prop];
}
}
return newObj;
}
2
3
4
5
6
7
8
9
# 扩展运算符
即组合使用 push,splice,concat 等方法
console.log([1,...[2,3,4],5])
// [1, 2, 3, 4, 5]
let age = {age: 15,hobby: "study"};
let name = {name: "Amy"};
let person = {...age, ...name};
console.log(person); // {age: 15, hobby: 'study', name: 'Amy'}
// ------------------------------------------------------------
let age2 = {age: 15,hobby: "study"};
let name = {name: "Amy"};
let {age} = {...age2};
let person = {age, ...name};
console.log(person); // {age: 15, name: 'Amy'}
2
3
4
5
6
7
8
9
10
11
12
13
const a1 = [1, 2];
const a2 = a1;
a2[0] = 2;
a1 // [2, 2]
2
3
4
5
注意:上面代码中,a2
并不是a1
的拷贝,而是指向同一份数据的另一个指针。修改a2
,会直接导致a1
的变化。
# Array.prototype.slice()
提取某个字符串的一部分,并返回一个新的字符串 不会改变原始数组。
[ )
let fruits = ['Banana', 'Orange', 'Lemon', 'Apple', 'Mango']
let citrus1 = fruits.slice(1, 3)
let citrus2 = fruits.slice(2);
// citrus1 ['Orange', 'Lemon']
// citrus2 ['Lemon', 'Apple', 'Mango']
2
3
4
5
浅拷贝
const fxArr = [{name: 'a',age:1},{name: 'b',age:2},{name: 'c',age:3}]
const fxArrs = fxArr.slice(0,2)
fxArrs[0].name = "love";
console.log(fxArr) // [{name: 'love',age:1},{name: 'b',age:2},{name: 'c',age:3}]
console.log(fxArrs) // [{name: 'love',age:1},{name: 'b',age:2}]
// ----------------------------------------------------------------
const fxArr = ["One", "Two", "Three"]
const fxArrs = fxArr.slice(0)
fxArrs[1] = "love";
console.log(fxArr) // ["One", "Two", "Three"]
console.log(fxArrs) // ["One", "love", "Three"]
2
3
4
5
6
7
8
9
10
11
# Array.prototype.concat()
用于合并两个或多个数组返回一个新数组 不会更改原始数组
const num1 = [1, 5, 3]
const num2 = [5, 5, 6]
const num3 = [7, 8, 9]
const numbers = num1.concat(num2, num3)
console.log(numbers)// [1, 5, 3, 5, 5, 6, 7, 8, 9]
2
3
4
5
如果省略了所有参数,则 concat
会返回调用此方法的现存数组的一个浅拷贝
const fxArr = [{name: "rio",age:14}]
const fxArrs = fxArr.concat()
fxArr[0].name = "love";
fxArrs[0].age = 15
console.log(fxArr) // [{name:"love",age:15}]
console.log(fxArrs) // [{name:"love",age:15}]
// ----------------------------------------------
const fxArr = ["name","rio","age",14]
const fxArrs = fxArr.concat()
fxArr[0]= "love";
fxArrs[1] = 15
console.log(fxArr) // ['love', 'rio', 'age', 14]
console.log(fxArrs) // ['name', 15, 'age', 14]
2
3
4
5
6
7
8
9
10
11
12
13
总结:对于数组方法Array的slice和concat方法不修改原数组,只会返回一个浅复制了原数组中的元素的一个新数组
如果该元素是个对象引用(不是实际的对象),会拷贝这个对象引用到新的数组里。两个对象引用都引用了同一个对象。如果被引用的对象发生改变,则新的和原来的数组中的这个元素也会发生改变。
对于字符串、数字及布尔值来说(不是 String、Number 或者 Boolean 对象),slice 会拷贝这些值到新的数组里。在别的数组里修改这些字符串或数字或是布尔值,将不会影响另一个数组。
# Object.assign()
Object.assign()
方法将所有可枚举(Object.propertyIsEnumerable()
返回 true)的自有Object.hasOwnProperty()
返回 true)属性
从一个或多个源对象复制到目标对象,返回修改后的对象。
const target = { a: 1, b: 2 }
const source = { b: 4, c: 5 }
const returnedTarget = Object.assign(target, source)
console.log(target) // { a: 1, b: 4, c: 5 }
console.log(returnedTarget === target) // true
2
3
4
5
6
浅拷贝只会将被复制对象的第一层属性进行复制,若第一层属性为原始类型的值,则直接复制其值,一般称之为“传值”;若第一层属性为引用类型的值,则复制的是其存储的指向堆内存对象的地址指针,一般称之为“传址”
let obj = {
name: "qw",
names: {
name1: 'fx',
name2: 'xka'
}
}
var newObj = Object.assign({}, obj);
obj.name='rio'
console.log(obj,newObj);
obj.names.name1 = 'qwe'
console.log(obj,newObj);
2
3
4
5
6
7
8
9
10
11
12
# 深拷贝原理及实现
深拷贝:深拷贝开辟一个新的栈,两个对象的属性完全相同,但是对应两个不同的地址,修改一个对象的属性,不会改变另一个对象的属性
# _.cloneDeep()
const _ = require('lodash');
const obj1 = {
a: 1,
b: { f: { g: 1 } },
c: [1, 2, 3]
};
const obj2 = _.cloneDeep(obj1);
console.log(obj1.b.f === obj2.b.f);// false
2
3
4
5
6
7
8
# JSON.stringify()
const obj2=JSON.parse(JSON.stringify(obj1));
// 弊端 会忽略undefined、symbol和函数
const obj = {
name: 'A',
name1: undefined,
name3: function() {},
name4: Symbol('A')
}
const obj2 = JSON.parse(JSON.stringify(obj));
console.log(obj2); // {name: "A"}
2
3
4
5
6
7
8
9
10
11
# 手写循环递归
function deepClone(obj, hash = new WeakMap()) {
if (obj === null) return obj; // 如果是null或者undefined我就不进行拷贝操作
if (obj instanceof Date) return new Date(obj);
if (obj instanceof RegExp) return new RegExp(obj);
// 可能是对象或者普通的值 如果是函数的话是不需要深拷贝
if (typeof obj !== "object") return obj;
// 是对象的话就要进行深拷贝
if (hash.get(obj)) return hash.get(obj);
let cloneObj = new obj.constructor();
// 找到的是所属类原型上的constructor,而原型上的 constructor指向的是当前类本身
hash.set(obj, cloneObj);
for (let key in obj) {
if (obj.hasOwnProperty(key)) {
// 实现一个递归拷贝
cloneObj[key] = deepClone(obj[key], hash);
}
}
return cloneObj;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
function deep(arr) {
if (typeof arr === "object") {
let res = Array.isArray(arr)? []:{};
for(let key in Object.keys(arr)){
res[key] = deep(arr[key]);
}
return res;
} else {
return arr
}
}
2
3
4
5
6
7
8
9
10
11