- 引用赋值与浅拷贝的区别
引用赋值是地址的赋值,将对象指针赋值给一个变量,让此变量指向对象。
浅拷贝只会将对象的各个属性进行依次复制,并不会进行递归复制。
var obj = { a:1, arr: [2,3] };
var obj1 = {};
var shallowObj = {};
var deepObj = {};
//引用赋值,obj1指向obj所指向的对象
var obj1 = obj;
//浅拷贝实现
for (var prop in obj){
if(obj.hasOwnProperty(prop)){
shallowObj[prop] = obj[prop];
}
}
//深拷贝,采用JSON.parse(JSON.stringify(obj))的方法实现,稍后讲解。
obj2 = JSON.parse(JSON.stringify(obj));
obj.arr[1] = 5;
console.log(obj1.arr[1]);//输出结果:5。其实,obj1与obj实为同一个对象
console.log(shallowObj.arr[1]);//输出结果:5。
console.log(deepObj.arr[1]); //输出结果:3。
复制代码
- 浅拷贝与深拷贝的区别
浅拷贝:因为浅拷贝只会将对象的各个属性进行依次复制,并不会进行递归复制,而 JavaScript 存储对象都是存地址的,所以浅拷贝会导致 obj.arr
和 shallowObj.arr
指向同一块内存地址,大概的示意图如下。意思就是将 obj
对象拷贝到 shallowObj
对象中,但不包括 obj
里面的子对象(arr
),也就是说shallowObj
只保存了 obj
的子对象的指针,但同为一个子对象。
obj
对象拷贝到
deepObj
对象中,包括
obj
里面的子对象。 3.
浅拷贝实现
(1)遍历赋值实现
var shallowCopy = function(obj) {
// 只拷贝对象
if (typeof obj !== 'object') return;
// 根据obj的类型判断是新建一个数组还是对象
var newObj = obj instanceof Array ? [] : {};
// 遍历obj,并且判断是obj的属性才拷贝
for (var key in obj) {
if (obj.hasOwnProperty(key)) {
newObj[key] = obj[key];
}
}
return newObj;
}
复制代码
(2)ES6扩展运算符
var obj = { a:1, arr: [2,3] };
var obj1 = {...obj}
复制代码
(3)数组方法(只适用于类数组对象)
Array.from(arrayLike)
从类数组对象或者可迭代对象中创建一个新的数组实例。返回一个新的数组。
var array1 = ['a', ['b', 'c'], 'd'];
var array2 = Array.from(array1);
array1[1][0] = 'e';
console.log(array2[1][0]);//输出结果为: "e"
复制代码
Array.prototype.concat()
用于合并两个或多个数组。此方法不会更改现有数组,而是返回一个新数组。
var array1 = ['a', ['b', 'c'], 'd'];
var array2 = array1.concat();
array1[1][0] = 'e';
console.log(array2[1][0]);//输出结果为: "e"
复制代码
Array.prototype.slice()
返回一个新的数组对象,这一对象是一个由 begin和 end(不包括end)决定的原数组的浅拷贝。原始数组不会被改变。
var array1 = ['a', ['b', 'c'], 'd'];
var array2 = array1.slice();
array1[1][0] = 'e';
console.log(array2[1][0]);//输出结果为: "e"
复制代码
(4)ES6方法Object.assign()
用于将所有可枚举属性的值从一个或多个源对象复制到目标对象。它将返回目标对象。
var obj = { a:1, arr: [2,3] };
var obj1 = Object.assign({}, obj);
obj.arr[1] = 5;
console.log(obj1.arr[1]);//输出结果:5。
复制代码
- 深拷贝实现
(1)借助JSON全局对象,然而使用这种方法会有一些隐藏的坑,它能正确处理的对象只有Number
,String
,Boolean
,Array
,扁平对象,即那些能够被json
直接表示的数据结构。
var obj = { a:1, arr: [2,3] };
var obj1 = JSON.parse(JSON.stringify(obj));
obj.arr[1] = 5;
console.log(deepObj.arr[1]); //输出结果:3。
复制代码
局限性:
- 会忽略 undefined
- 会忽略 symbol
- 不能序列化函数
- 不能解决循环引用的对象
var arr = [function(){
console.log(a)
}, {
b: function(){
console.log(b)
}
}]
var new_arr = JSON.parse(JSON.stringify(arr));
console.log(new_arr);
复制代码
执行结果:
(2) 浅拷贝+递归: 当遇到子对象是引用对象时,采用递归层层复制。var deepCopy = function(obj) {
if (typeof obj !== 'object') return;
var newObj = obj instanceof Array ? [] : {};
for (var key in obj) {
if (obj.hasOwnProperty(key)) {
newObj[key] = typeof obj[key] === 'object' ? deepCopy(obj[key]) : obj[key];
}
}
return newObj;
}
var obj = { a:1, arr: [2,3] };
var obj1 = deepCopy(obj);
obj.arr[1] = 5;
console.log(obj.arr[1]); //输出结果:3。
复制代码
存在问题:
- 没有对传入参数进行校验,传入 null 时应该返回 null 而不是 {}
let newObj = deepCopy({value:null});
console.log(newObj);//输出结果为:{value:{}}
复制代码
- 对于对象的判断逻辑不严谨,因为 typeof null === 'object'
- 没有考虑数组的兼容 (3)jQuery版的extend
推荐两篇文章吧:
- 面试题之如何实现一个深拷贝
- JavaScript专题之从零实现jQuery的extend
- 各语言深拷贝比较
(1)jQuery —— $.clone()
/ $.extend()
在 Underscore 中有这样一个方法:_.clone()
,这个方法实际上是一种浅复制 (shallow-copy),所有嵌套的对象和数组都是直接复制引用而并没有进行深复制。来看一下例子应该会更加直观:
_.clone()
/ _.cloneDeep()
在lodash中关于复制的方法有两个,分别是_.clone()
和_.cloneDeep()
。其中_.clone(obj, true)
等价于_.cloneDeep(obj)
。更厉害的是,lodash 针对存在环的对象的处理也是非常出色的。因此相较而言,lodash 在深复制上的行为反馈比前两个库好很多,是更拥抱未来的一个第三方库。
参考文章:
- js 深拷贝 vs 浅拷贝
- javascript中的深拷贝和浅拷贝?