原创:牛津小马哥web前端工程师陈小妹妹。
应用场景:
在程序中,我们有时候需要将对象作为参数传递处理。有些人只是为了拿到对象的某些私有属性做一个数据处理,而有些人是为了拿到原型链上属性等等。如何拿到正确的对象来应用于这两个简单的情景呢?
这就是我们今天要讲的知识点,对象的深克隆和浅克隆
原生深克隆方法JSON.parse / stringify
这是一种会丢失某些数据的克隆方法。
如果你不使用Date,function,undefined,Infinity,正则表达式,Map集合,Set集合,Bolbs,FileLists,ImageDatas,稀疏数组sparse Arrays,类型化数组Typed Arrays或其他复杂类型的对象中,一个很简单的深克隆的对象方法就是:
JSON.parse(JSON.stringify(object))
以下为演示例子:
const a = {
string: 'string',
number: 123,
bool: false,
nul: null,
date: new Date(), // stringified
undef: undefined, // lost
inf: Infinity, // forced to 'null'
re: /.*/, // lost
}
console.log(a);
console.log(typeof a.date); // Date object
const clone = JSON.parse(JSON.stringify(a));
console.log(clone);
console.log(typeof clone.date); // result of .toISOString()
打印出结果如下
可以发现使用JSON.parse(JSON.stringify(object))丢失的数据类型比较多。所以在使用它的时候,我们需要清楚的知道目标对象object的数据类型。一般来说,如果我们需要处理的数据只有number,string,null类型,才推荐使用这个方法进行数据对象的克隆。
使用库进行深克隆
由于克隆对象并非易事(复杂类型,循环引用,函数等),因此大多数主要库都提供了克隆对象的功能。如果您已经在使用的库已经提供了克隆方法,你可以直接使用,大多数情况下库的克隆方法总比你自己写的完善得多。
lodash:_.cloneDeep方法
可以通过lodash.clonedeep模块单独导入,如果您尚未使用提供深度克隆功能的库,则可能是您的最佳选择
Example
var objects = [{ 'a': 1 }, { 'b': 2 }];
var deep = _.cloneDeep(objects);
console.log(deep[0] === objects[0]);
// => false
AngularJS:angular.copy方法(Deep Copy)
angular.copy(source, [destination]);
jQuery:jQuery.extend(true, { }, oldObject);
<script>
var object1 = {
apple: 0,
banana: { weight: 52, price: 100 },
cherry: 97
};
var object2 = {
banana: { price: 200 },
durian: 100
};
// Merge object2 into object1, recursively
$.extend( true, object1, object2 );
// Assuming JSON.stringify - not available in IE<8
$( "#log" ).append( JSON.stringify( object1 ) );
</script>
输出结果
{"apple":0,"banana":{"weight":52,"price":200},"cherry":97,"durian":100}
jQuery还有一个.clone()的方法,仅克隆DOM元素。第一个参数为true,表示递归深层复制。这里不展开讨论。
ES6的浅克隆
为了完整起见,请注意ES6提供了两种浅表复制机制:Object.assign()和扩展运算符。
Object.assign()
语法:Object.assign(target, ...sources)
MDN上说了:
Object.assign()拷贝的是属性值。假如源对象sources的属性值是一个对象的引用,那么它也指向那个引用。
如果源对象的属性值为简单类型(string, number),通过Object.assign({},obj1);那么会得到这个属性值的独立拷贝;如果属性值为对象或其它引用类型,那么它会指向这个对象的引用。这是Object.assign()特别值得注意的地方。
let obj1 = { a: 0 , b: { c: 0}};
let obj2 = Object.assign({}, obj1);
console.log(JSON.stringify(obj2)); // { a: 0, b: { c: 0}}
//源对象的属性值为简单类型
obj1.a = 1;
console.log(JSON.stringify(obj1)); // { a: 1, b: { c: 0}}
console.log(JSON.stringify(obj2)); // { a: 0, b: { c: 0}}
//源对象的属性值为引用类型
obj2.b.c = 3;
console.log(JSON.stringify(obj1)); // { a: 1, b: { c: 3}}
console.log(JSON.stringify(obj2)); // { a: 0, b: { c: 3}}
扩展运算符
var arr = [1, 2, 3];
var arr2 = [...arr]; // like arr.slice()
arr2.push(4);
// arr2 此时变成 [1, 2, 3, 4]
// arr 不受影响
提示: 实际上, 展开语法和 Object.assign() 行为一致, 执行的都是浅拷贝(只遍历一层)。
关于对象的克隆的内容到此为止~感谢阅读!