转自:http://blog.csdn.net/qq_27090183/article/details/50823429
使用递归来实现一个深度克隆,可以复制一个目标对象,返回一个完整拷贝
被复制的对象类型会被限制为数字、字符串、布尔、日期、数组、Object对象。不会包含函数、正则对象等
首先要去判断要克隆的对象的值类型或者引用类型。判断方法有很多种!
对于值类型或者引用有4种方法判断:
1.typeof
但是!js的数值有两种构造方法:直接赋值法和通过值函数构造器构造
例如:
var test1 = "string";
var test2 = new String("string2");
console.log(typeof test1);//输出string
console.log(typeof test2);//输出object
对于typeof
来说;所有通过构造器constructor
产生的变量都是object
.那么我们怎么去判断用constructor
产生的变量?
2.instanceof
instanceof
函数可以判断左边参数是否是右边参数的一个实例!
例如:
console.log(test2 instanceof String);//输出true
console.log(test1 instanceof String);//输出false
这就很不和谐了!有没有两种都能判断的方法呢?
3.Object.prototype.toString.call()
当然有!MDN在官方教程上就介绍了一种可以判断所有类型的方法!
使用toString()方法来检测对象类型
console.log(Object.prototype.toString.call(test1))//输出[object String]
console.log(Object.prototype.toString.call(test1))//输出[object String]
顿时觉得人生豁然开朗了起来!啊~五环!你比四环多一环!
4.Object.constructor
在研究MDN 的api文档的时候,发现了constructor
方法!
无论怎么样,这个方法都返回一个指向创建了该对象原型的函数引用。
需要注意的是,该属性的值是那个函数本身, 而不是一个包含函数名称的字符串!
对于原始值(如1,true 或 “test”),该属性为只读。 所有对象都会从它的原型上继承一个constructor
属性. 所以,虽然可以实现判断,但是还是不用为好!
console.log(test1.constructor.toString()=="function String() { [native code] }")//true;
console.log(test2.constructor.toString()=="function String() { [native code] }")//true;
总结:方法3、4对于所有值类型和引用类型适用(推荐第三种方法,毕竟官方),第12种方法看情况使用!
然后来到遍历问题了!
1.字符串的遍历
var temp = src.split("");
var cloneString="";
for(var i=0;i<temp.length;i++)
{
cloneString+=temp[i];
}
原理就是利用split()
方法将字符串里一个个字母分开(注意里面的参数为空 "",为其他就会以这个参数为标准分离字符串)
2.数组的遍历
使用的是传统的数组遍历
var temp = new Array();
for(var i=0,a;a = array[i];i++)
{
temp[i] = cloneObject(a);
}
这里遇到了几个坑,先说一下: 当使用
for(var a in array)
{
console.log(a+typeof a)
}
得到的值是 0 string 1 string 2 string
这种方法并不行 还有一个坑就是当使用
for(var i=0,a;a = array[i++];)
时 i会在a
被赋值后就自动增加而不是等到一个循环完成再增加 ;也就是遍历结果是对的,但是i
的数值变化是从1
开始而不是从0
开始的!
在赋值的过程中,我首先使用的是temp.push()
方法!但是!push
方法会让temp
数组新增加的元素的类型为undefined
!这不是我想要的结果!我要的是完美克隆,即数组里面对象类型也要和原来的一致。
看了MDN的api接口发现解决方法如下:
Array.prototype.push.apply(temp,array);
3.对象的遍历
var temp = {}; var keys = Object.keys(src); // keys 为对象src的键名字数组
// 它是数组!!!
for(var i=0,a;a=keys[i];i++)
{
temp[a] = cloneObject(src[a]);
}
对象的遍历,首先获得它的键数组(对象自带的keys()
方法),然后再通过键遍历一次值就行了,很简单。
多看MDN总会有收获!下面附上我的代码!
var cloneObject = function(src){
var Result;
switch(Object.prototype.toString.call(src)){
case "[object Number]":
Result = (typeof src === "object"?new Number(src):parseInt(src.toString()));
break;
case "[object String]":
// 遍历字符串 =.= 好像没啥意义
// {
// var temp = src.split("");
// var cloneString="";
// for(var i=0;i<temp.length;i++)
// {
// cloneString+=temp[i];
// }
// }
Result = (typeof src === "object"?new String(src):src.toString());
break;
case "[object Boolean]":
Result = (typeof src === "Boolean"?new Boolean(src):src);
break;
case "[object Date]":
Result = new Date(src);
break;
case "[object Array]":
var temp = new Array();
// Array.prototype.push.apply(temp,src);
// 当使用for(var i=0,a;a = src[i++];) i会在a被赋值后就自动增加而不是
// 等到一个循环完成再增加
for(var i=0,a;a = src[i];i++)
{
// temp.push(cloneObject(a));
// 使用push方法会让数组所有元素的类型变成undfined
temp[i] = cloneObject(a);
}
Result = temp;
delete temp;
break;
case "[object Object]":
var temp = {};
var keys = Object.keys(src);
// keys 为对象src的键名字数组
// 它是数组!!!
for(var i=0,a;a=keys[i];i++)
{
temp[a] = cloneObject(src[a]);
}
Result = temp;
delete temp;
delete keys;
break;
default:
break;
}
return Result;
}