认识深拷贝和浅拷贝
javascript 中一般有按值传递和按引用传递两种赋值方式:
- 按值传递的是基本数据类型(Number, String, Boolean, Null, Undefined, Symbol),一般存放于内存中的栈区,存取速度快,存放量小;
- 按引用传递的是引用类型(Object, Array, Function),一般存放与内存中的堆区,存取速度慢,存放量大,其引用指针存于栈区,并指向引用本身。
深拷贝和浅拷贝是相对于引用类型而言的:
- 浅拷贝: 只拷贝对象的引用
- 深拷贝:指复制后的新对象重新指向一个新的内存地址,两个对象改变互不影响
浅拷贝
对象类型在赋值的过程中其实是复制了地址,从而会导致改变了一方其他也都被改变的情况。
let a = {
age: 1
}
let b = a
a.age = 2
console.log(b.age) // 2
浅拷贝的方法
Object.assign、...运算法、slice、concat
filter和map是深拷贝吗?不是,当是嵌套数组时,第一层深拷贝,后面的都是浅拷贝
1. Object.assign
let a = {
age: 1
}
let b = Object.assign({}, a)
a.age = 2
console.log(b.age) // 1
console.log(b === a) // false
嵌套对象时,第一层深拷贝,后面的都是浅拷贝
var obj1 = {name:{a:1}};
var obj2 = Object.assign({}, obj1);
obj2.name.a = 2;
console.log(obj1.name); // {a:2}
console.log(obj2.name);// {a:2}
2. 展开运算符 ...
嵌套对象时,第一层深拷贝,后面的都是浅拷贝
let a = {
age: 1
}
let b = { ...a }
a.age = 2
console.log(b.age) // 1
3. slice
嵌套数组时,第一层深拷贝,后面的都是浅拷贝
let arr1 = [1, 2, [3]]
let arr2 = arr1.slice(0)
arr1[2][0] = 4
// 4
arr2[2][0]
4. concat
嵌套数组时,第一层深拷贝,后面的都是浅拷贝
let arr1 = [1, 2, [3]]
let arr2 = arr1.concat()
arr1[2][0] = 4
// 4
arr2[2][0]
深拷贝的方法
1. JSON.parse(JSON.stringify(object))
该方法的局限性
- 丢失
undefined - 丢失
symbol - 丢失函数
- 不能解决循环引用的对象
- 正则
Date类型由Date变为stringNaN、Infinity变为nullset、map丢失
测试用例
function Obj() {
this.func = function() {
console.log(1)
};
this.obj = {a : 1};
this.arr = [1, 2, 3];
this.und = undefined;
this.reg = /123/;
this.date = new Date(0);
this.NaN = NaN;
this.infinity = Infinity;
this.sym = Symbol();
this.set = new Set([1, 2, 3]);
this.map = new Map([['a', 1], ['b', 2]]);
}
let obj1 = new Obj();
Object.defineProperty(obj1, 'innumerable', {
enumerable: false,
value: 'innumerable'
});
let obj2 = JSON.parse(JSON.stringify(obj1));
结果对比
// obj1
Obj {
func: [Function],
obj: { a: 1 },
arr: [ 1, 2, 3 ],
und: undefined,
reg: /123/,
date: 1970-01-01T00:00:00.000Z,
NaN: NaN,
infinity: Infinity,
sym: Symbol(),
set: Set { 1, 2, 3 },
map: Map { 'a' => 1, 'b' => 2 }
}
// obj2
{
obj: { a: 1 },
arr: [ 1, 2, 3 ],
reg: {},
date: '1970-01-01T00:00:00.000Z',
NaN: null,
infinity: null,
set: {},
map: {}
}
2. lodash
// 安装
npm i --save lodash
// 引入
const _ = require('lodash')
// 使用
let obj = {
a: {
a: 'hello'
},
b: 33
}
let newObj = _.cloneDeep(obj)
newObj.a.a = 'world'
console.log(obj.a.a)
console.log(obj === newObj)
console.log(obj == newObj)
// hello
// false
// false
3. 实现一个简单的深拷贝
const isComplexDataType = obj => (
(typeof obj === 'object' || typeof obj === 'function') && (obj !== null)
);
const deepClone = function (obj, hash = new WeakMap()) {
// 解决循环引用,如 obj.loop = obj;
if (hash.has(obj)) {
return hash.get(obj);
}
let type = [Date, RegExp, Set, Map, WeakSet, WeakMap];
if (type.includes(obj.constructor)) {
return new obj.constructor(obj);
}
// 获取 obj 属性描述对象
let allDesc = Object.getOwnPropertyDescriptors(obj);
// 1、创建一个对象;2、确定继承关系;3、把属性值赋给新对象
let cloneObj = Object.create(Object.getPrototypeOf(obj), allDesc);
hash.set(obj, cloneObj);
// Reflect.ownKeys(obj)可以拷贝不可枚举属性和 symbol 类型
for(let key of Reflect.ownKeys(obj)) {
// 如果值是引用类型(非函数)则递归调用 deepClone
cloneObj[key] = (isComplexDataType(obj[key]) && typeof obj[key] !== 'function')
? deepClone(obj[key], hash)
: obj[key];
}
return cloneObj;
}
测试用例
function Obj() {
this.func = function() {
console.log(1)
};
this.obj = {a : 1};
this.arr = [1, 2, 3];
this.und = undefined;
this.reg = /123/;
this.date = new Date(0);
this.NaN = NaN;
this.infinity = Infinity;
this.sym = Symbol();
this.set = new Set([1, 2, 3]);
this.map = new Map([['a', 1], ['b', 2]]);
}
let obj1 = new Obj();
let obj2 = deepClone(obj1);
拷贝结果
Obj {
func: [Function],
obj: { a: 1 },
arr: Array { '0': 1, '1': 2, '2': 3 },
und: undefined,
reg: /123/,
date: 1970-01-01T00:00:00.000Z,
NaN: NaN,
infinity: Infinity,
sym: Symbol(),
set: Set { 1, 2, 3 },
map: Map { 'a' => 1, 'b' => 2 }
}
Object.getPrototypeOf()
function Bar() {}
let obj = new Bar();
Object.getPrototypeOf(obj) == Bar.prototype
// true
Reflect.ownKeys() 与 Object.keys() 区别:
Reflect.ownKeys()返回所有的属性 key,包括不可枚举类型,Symbol类型,不包括继承的属性Object.keys()返回遍历对象可枚举的属性,不包括继承的属性和Symbol类型
let obj = {
a: 1,
b: 2
}
Object.defineProperty(obj, Symbol(), {
value: 1,
enumerable: true
})
Object.defineProperty(obj, 'method', {
value: function() {
console.log('ok')
},
enumerable: false
})
console.log(Reflect.ownKeys(obj));
// [ 'a', 'b', 'method', Symbol() ]
console.log(Object.keys(obj));
// [ 'a', 'b' ]