Object.assign
从一个或多个源对象分配到目标对象,返回目标对象
1 2 3 4 5 6 7 8 9 10 11 12
| let obj1 = { a: { b: 1 }, sym: Symbol(1) }; Object.defineProperty(obj1, 'innumerable', { value: '不可枚举属性', enumerable: false, }); let obj2 = {}; Object.assign(obj2, obj1);
obj1.a.b = 2;
console.log({ obj1 }); console.log({ obj2 });
|
- 不会拷贝不可枚举属性
- 可以拷贝 symbol 类型的属性
- 他不会拷贝对象的继承属性
… 扩展运算符
和Object.assign
拥有同样的缺陷,但是拷贝基本类型的值的时候会更加方便
concat
用于合并两个或多个数组。此方法不会更改现有数组,而是返回一个新数组
slice
浅拷贝数组,仅仅针对数组类型。
类数组可以使用 Object.prototype.slice.call(arr)
,表示从 Array 中获取 slice 方法,绑定到 arr 中,并执行 slice。
测试:
array1
和cloneArray1
第一项的 name 属性都变为’wangwu’
1 2 3 4 5 6 7 8 9 10 11
| const array1 = [ { id: 1, name: 'zhangsan' }, { id: 2, name: 'lisi' }, ];
const cloneArray1 = array1.slice(); cloneArray1[0].name = 'wangwu'; console.log('array1', array1); console.log('cloneArray1', cloneArray1);
|
浅拷贝的实现
1 2 3 4 5 6 7 8 9 10 11
| function shallowClone(obj) { if (typeof obj !== 'object' || target === null) return; const newObj = Array.isArray(obj) ? [] : {}; for (const key in obj) { if (obj.hasOwnProperty(key)) { newObj[key] = obj[key]; } } return newObj; }
|
深拷贝的实现
乞丐版:JSON.stringify
将对象序列化为 JSON 字符串,对对象里面的内容转为字符串,再用 JSON.parse 生成新对象。
注意点:
- 如果对象”值”中有函数、undefined、symbol 这几种类型,经过序列化之后这个键值对会消失
- 拷贝 Date 引用各类型会变成字符串
- 拷贝 RegExp 引用类型会变成空对象
- 无法拷贝不可枚举属性
- 无法拷贝对象的原型链
- 无法拷贝对象的循环应用,即对象成环(obj[key] = obj)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| function Obj() { this.func = function () { alert(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(1); this[Symbol(2)] = 'sym2'; }
let obj1 = new Obj();
Object.defineProperty(obj1, 'innumerable', { enumerable: false, value: 'innumerable', }); console.log('obj1', obj1); let obj22 = JSON.parse(JSON.stringify(obj1)); console.log('obj22', obj22);
|

基础版
注意点:
- 不能复制不可枚举和 Symbol 类型的属性
- 对于 Date、RegExp 这样的引用类型不能正确的拷贝
- 没解决循环引用问题
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| function deepClone(target) { if (typeof target === 'object' && target !== null) { const newObj = Array.isArray(target) ? [] : {}; for (const key in target) { if (typeof target[key] === 'object') { newObj[key] = deepClone(target[key]); } else { newObj[key] = target[key]; } } return newObj; } }
const obj3 = deepClone(obj1); console.log('obj3', obj3);
|

改进版
- 对于不可枚举和 Symbol 类型,可以 Reflect.ownKeys(),Reflect.ownKeys === Object.getOwnPropertyNames(obj).concat(Object.getOwnPropertySymbols(Obj))
- 对于 Date 和 RegExp 类型,直接返回一个实例对象
- Object.getOwnPropertyDescriptors 可以获取所有自身属性的描述符,以及对应的特性,顺便结合 Object 的 create 方法创建一个新对象,并集成原对象的原型链
- 利用 WeakMap 类型作为 hash 表,因为 WeakMap 是弱引用类型,可以有效防止内存泄漏,如果存在循环引用,就直接返回 WeakMap 存储的值
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| const isComplexDataType = (obj) => (typeof obj === 'object' || typeof obj === 'function') && obj !== null;
function deepCloneImprove(obj, hash = new WeakMap()) { if (obj.constructor === Date) return new Date(obj); if (obj.constructor === RegExp) return new RegExp(obj); if (hash.has(obj)) return hash.get(obj); let allDesc = Object.getOwnPropertyDescriptors(obj); let cloneObj = Object.create(Object.getPrototypeOf(obj), allDesc); hash.set(obj, cloneObj);
for (let key of Reflect.ownKeys(obj)) { cloneObj[key] = isComplexDataType(obj[key]) && typeof obj[key] !== 'function' ? deepCloneImprove(obj[key], hash) : obj[key]; } return cloneObj; }
|
验证代码
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
| let obj4 = { num: 0, str: '', boolean: true, unf: undefined, nul: null, obj: { name: '我是一个对象', id: 1 }, arr: [0, 1, 2], func: function () { console.log('我是一个函数'); }, date: new Date(0), reg: new RegExp('/我是一个正则/ig'), [Symbol('1')]: 1, }; Object.defineProperty(obj4, 'innumerable', { enumerable: false, value: '不可枚举属性', }); obj4 = Object.create(obj4, Object.getOwnPropertyDescriptors(obj4)); obj4.loop = obj4; let cloneObj = deepCloneImprove(obj4); cloneObj.arr.push(4); console.log('obj4', obj4); console.log('cloneObj', cloneObj);
|
