在实际项目中,我们往往需要对一个对象进行拷贝,其目的可能是为了拷贝一个对象做其他处理,可能是为了扩充一个对象使其拥有另一个对象的属性,也可能是为了其他的目的
但是在 js 中,对象的拷贝分为深浅两种,而不仅仅是将这个对象的属性赋给另一个对象那么简单。同时对象的深浅拷贝也是很对前端面试中常见的一题,所以下面就来详细说说 js 中的 对象深浅拷贝,如果有误,还请多多指正,共同进步
值类型与引用类型
首先理解一下两个概念:值类型和引用类型
- 值类型
简单来说,值类型就是将一个变量赋值给另一个变量后,两个变量完全独立,改变其中的一个并不会影响另一个
var a = 1;
var b = a; // b = 1
a = 2; // a = 2 b = 1
像上面的例子中,虽然后声明的变量b赋予了a的值,但是改变a的值,b却没有改变
除了数值类型,与此类似的 js 中的值类型还有布尔值、字符串、null、undefined等
- 引用类型
引用类型刚好与值类型相反,原始的变量被改变后,被赋值的变量也会被改变
引用类型会在内存中开辟一块区域保存它的值,而被赋予了这个值的原始变量本质上是将指向了这块内存,而被赋值的另一个变量获得的也只是这个指向而已
所以一旦内存上的值改变,所有只想这块内存的变量的值都会被改变
var c = [1,2,3];
var d = c; // d = [1,2,3]
c[0] = 0; // c = [0,2,3] d = [0,2,3]
可能有的小伙伴会说,不对呀,下面这种情况 d 并没有被改变
var c = [1,2,3];
var d = c; // d = [1,2,3]
c = [4,5,6]; // c = [4,5,6] d = [1,2,3]
其实并没有不对,上面例子中的 c[0] 改变的是原内存地址中存储的值,因为c、d指向相同,所有都被改变;而后一个例子中,为 c 重新赋值,相当于是在内存中重新开辟了一块区域存储新值,改变了 c 原来的指向,但 d 的指向却没有改变,所以我们看到的值也就没变
js 中常见引用类型有 数组、对象、函数
了解了值类型和引用类型,下面我们开始进入正题
对象的浅拷贝
对象的浅拷贝简单,就是将一个变量赋给另一个变量
var obj1 = {
name: 'test name',
age: 18
}
var obj2 = obj1;
上面的例子中 obj2 经过浅拷贝拥有了 obj1 的属性
封装浅拷贝方法
var easyCopy = function ( extendObj ) {
var newObj = extendObj.constructor === Array ? [] : {};
if (typeof extendObj != 'object') return;
for (var key in extendObj) {
if (extendObj.hasOwnProperty(key)) {
newObj[key] = extendObj[key];
}
}
return newObj
};
var obj2 = {
tall: 1.8,
weight: 75
}
var obj1 = easyCopy( obj2 );
console.log( obj1 );
浅拷贝存在的问题
我们知道引用类型的赋值其实是改变了变量的指向,那么如果在需要拷贝的对象中存在某个属性的值是引用类型,如数组或子对象,那么浅拷贝后的原对象的属性获得的也只是这个指向
所以如果改变被拷贝对象的属性值,那么原对象的相应属性也会跟着改变
var obj2 = {
names: ['test0', 'test1', 'test3']
}
obj1 = easyCopy( obj2 );
console.log( obj1, obj2 );
obj2.names[1] = 'test0';
console.log( obj1, obj2 );
// 打印结果为:obj1.name[1] 的值从原来的 'test1' 变成了 'test0'
日常项目中使用比较多的是浅拷贝,但是如果某些情况下使用了浅拷贝,可能会产生一些极不容易发现的bug,所以这时候就需要用到深拷贝了
对象的深拷贝
深拷贝其实就是将对象中的数组、子对象进行深度递归遍历,直到其不是引用类型位置再进行复制,这样即使改变了其中一个的值,也不会影响到另一个
深拷贝的封装
var deepCopy = function( extendObj ){
var str, newObj = extendObj.constructor === Array ? [] : {};
if(typeof extendObj !== 'object'){
return;
} else if(window.JSON){
str = JSON.stringify(extendObj);
newObj = JSON.parse(str);
} else {
for(var key in extendObj){
if (!extendObj.hasOwnProperty(key)) return;
newObj[key] = typeof extendObj[key] === 'object' ?
cloneObj(extendObj[key]) : extendObj[key];
}
}
return newObj;
};
var obj2 = {
names: ['test0', 'test1', 'test3']
}
var obj1 = deepCopy( obj2 );
console.log( obj1, obj2 );
obj2.names[1] = 'test0';
console.log( obj1, obj2 );
深拷贝的缺点
虽然深拷贝能够避免浅拷贝出现的问题,但是却会带来性能上的问题,如果一个对象非常复杂或数据庞大,所消耗的性能将会是很可观的
补充
** for … in**
for … in 可以用来遍历任何一个对象,它会将该对象上的所有属性全部遍历出来,包括原型链上的属性
由于可以遍历出原型链上的属性,所以需要使用 hasOwnProperty 这个方法来判断到底是不是这个对象自身的属性
由于数组也是对象,for … in 也可以用来遍历数组,但是 for … in 损耗性能较多,所以如果是遍历数组的话最好使用 for 语句
递归调用
一个方法重复调用自身的情况叫做递归,但是需要注意的是,一定要有一个条件来结束递归,否则将会陷入无限的循环
var index = 1;
function fuckSelf() {
if (index < 100) {
index++;
fuckSelf();
}
}
fuckSelf();