递归方法
递归函数主要是在函数内自己调用自己的一种函数。本文借助该方法详细描述在实现对象深克隆的工程中,递归的每一个层次干了什么工作。
深克隆
对象的深克隆可以保证克隆出的对象与被克隆对象间,在内存地址中,指向完全不同的两个地址。也就是两者之间实现彻底的克隆,即互不打扰。
详细解析
首先,在js中创建出被克隆对象,对象名称为old_obj
var old_obj = {
namme: '小明',
age: '21',
hobbies: ['游戏', '编程'],
bestfood: {
name1: '汉堡包',
name2: '薯条'
}
};
// 该old_obj对象属性有基本类型,有引用型(数组,对象)
接着进行定义克隆出的对象,当然为一个空的对象new_obj
// 定义一个新的对象,用于克隆
var new_obj = {};
下面进行深克隆,我们定义一个克隆函数deepclone(),其中两个参数:传入的第一个参数为新克隆出的对象,传入的第二参数为被克隆对象。也就是new_obj, old_obj
var deepclone() = function (newobj, oldobj){
}
对于最后的函数调用语句为
// 调用该函数
deepclone(new_obj, old_obj);
// 打印新克隆出的obj对象
console.log(new_obj);
接下来对于deepclone()函数内部语句进行描述。我们不妨先进行对他的浅克隆(当然你要知道这是浅克隆,毕竟好的代码都是一步一步地进行更新与修改)
var deepclone = function (newobj, oldobj) {
for (var k in oldobj) {
newobj[k] = oldobj[k];
}
}
对于浅克隆中第二行代码解析:oldobj为old_obj对象,k为遍历的每项对象的属性名。
newobj[k] = oldobj[k];
在浅克隆中newobj[k] 就表示新的对象的该属性的值为什么什么,右边的oldobj[k]为old_obj的该属性的值,让遍历到的该属性值赋值给新的数组对象里。举一个例子,对于第一此遍历,该行的功能就是将old_obj.name值赋给new_obj,但是new_obj内目前只是空对象,newobj[k]对于第一次遍历相当于在new_obj对象中添加name属性,并且值要接收old_obj.name的值,然后再进行第二次、第三次、直到遍历完所有。浅克隆结束
下面在该基础上进行更新改正。首先我们要知道引用型数据类型(数组、对象等)我们要克隆就必须进行深克隆,但是为了区分数组和对象,就需要用到if语句。下面我们先解决当碰到数组时怎么解决
var deepclone = function (newobj, oldobj) {
for (var k in oldobj) {
// 遍历前 判断是否为数组
if (Array.isArray(oldobj[k])) {
// 新对象的该属性先定义一个空数组
newobj[k] = [];
deepclone(newobj[k], oldobj[k]);
} else {
// 如果不为数组,不为对象
newobj[k] = oldobj[k];
}
}
}
我们首先要将判断语句加在for遍历语句内,进行判断
// 遍历前 判断是否为数组
if (Array.isArray(oldobj[k])) {
// 新对象的该属性先定义一个空数组
newobj[k] = [];
deepclone(newobj[k], oldobj[k]);
}
对于该判断语句,进行详细解答
Array.isArray(oldobj[k])
该语句是判断当前遍历到的old_obj对象属性值是否为数组,当为数组时,进行if语句内语句的执行
// 遍历前 判断是否为数组
if (Array.isArray(oldobj[k])) {
// 新对象的该属性先定义一个空数组
newobj[k] = [];
deepclone(newobj[k], oldobj[k]);
}
因为我们要就将数组值进行深克隆,设法要接受他。第一步是在新克隆出的对象new_obj内添加遍历到的该属性值的属性,并且值赋值为空数组。这是为了方便接收
newobj[k] = [];
接着最重要的一步,我们进行函数递归,再次调用此deepclone()函数
deepclone(newobj[k], oldobj[k]);
下面我们详细解答该递归的过程。对于我们的案例,当for语句遍历old_obj,当k为第三项即old_obj[k] = hobbies时,进入if语句,if语句判定为数组,进入if语句内,newobj[k] = []会在new_obj对象内添加hobbies属性名。接着进行deepclone(newobj[k], oldobj[k])语句。此时deepclone()函数再次执行。但是!此时deepclone()函数里接收的第一个参数为new_obj.hobbies也就是一个空数组,第二个参数是old_obj.hobbies也就是['游戏', '编程']该数组,即传入的为两个数组。传入两个数组后,进入for循环遍历。注意!此时的var k in oldobj中,oldobj为['游戏', '编程']数组,k为数组的下标(0,1)。进入if语句,进行判断。对于第一个值“游戏”当然不是一个数组,那么跳过此if语句,进入else语句内 newobj[k] = oldobj[k];此语句翻译一下就是new_obj.hobbies[0] = old_obj.hobbies[0],也就是将old_obj的hobbies数组下标为0的值赋值给new_obj.hobbies中下标为0的值,这样经过for语句遍历将该数组进行深克隆。
接下来解决对象深克隆问题,对于对象的深克隆问题其实与数组的解决办法相似。我们添加else if语句
else if (typeof oldobj[k] == 'object') {
// 如果遍历的该项是obj类型
newobj[k] = {};
deepclone(newobj[k], oldobj[k]);
}
第一行代码typeof oldobj[k] == 'object',进行判断当old_obj遍历到的元素值为一个对象,才进入else if内。首先进入else if内,我们先在new_obj对象内添加该属性,并且将该属性值设置为空。对与本文示例,当遍历到
{
name1: '汉堡包',
name2: '薯条'
}时,判断是一个对象,进入else if内,接着执行newobj[k] = {}语句,他的作用是在new_obj对象内添加bestfood属性,并且属性值为空,是一个空对象。接着进入deepclone(newobj[k], oldobj[k])语句。该语句就进行了函数递归,deepclone()函数再次被调用。注意!此时传入deepclone的第一个参数是new_obj的bestfood的空对象,传入的第二个参数是old_obj的bestfood的属性值:
{
name1: '汉堡包',
name2: '薯条'
}
此时,再次进入for循环遍历for (var k in oldobj)。注意!此时的oldobj为
{
name1: '汉堡包',
name2: '薯条'
},k为属性名也就是“name1, name2”.接下来进行if判断,此时第一此遍历,k = 'name1',不为数组。再进入else if中,k = 'name1'不为对象,最后进入else语句newobj[k] = oldobj[k],意思在new_obj的bestfood中建立一个属性名为name1,值是“汉堡包”,也就是将“汉堡包”赋值给new_obj.bestfood的属性名为name1。接着再进行遍历直到对象遍历完,实现对象的深克隆。
最后打印出克隆出的对象new_obj
// 打印新克隆出的obj对象
console.log(new_obj);
进行测试
// 测试
console.log('-------------------------');
old_obj.hobbies.push('品尝美食');
console.log(old_obj);
console.log(new_obj);
运行测试代码如下:
可以看出在old_obj内,对其hobbies数组中再次推入新的属性值“品尝美食”,对其bestfood内再次添加新对象属性“name3” ,值为“巧克力”。可见并不会对新克隆出的new_obj产生任何影响。
整个示例代码
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<script>
var old_obj = {
namme: '小明',
age: '21',
hobbies: ['游戏', '编程'],
bestfood: {
name1: '汉堡包',
name2: '薯条'
}
};
// 该old_obj对象属性有基本类型,有引用型(数组,对象)
// 定义一个新的对象,用于克隆
var new_obj = {};
// 进行深克隆
var deepclone = function (newobj, oldobj) {
for (var k in oldobj) {
// 遍历前 判断是否为数组
if (Array.isArray(oldobj[k])) {
// 新对象的该属性先定义一个空数组
newobj[k] = [];
deepclone(newobj[k], oldobj[k]);
} else if (typeof oldobj[k] == 'object') {
// 如果遍历的该项是obj类型
newobj[k] = {};
deepclone(newobj[k], oldobj[k]);
} else {
// 如果不为数组,不为对象
newobj[k] = oldobj[k];
}
}
}
// 调用该函数
deepclone(new_obj, old_obj);
// 打印新克隆出的obj对象
console.log(new_obj);
// 测试
console.log('-------------------------');
old_obj.hobbies.push('品尝美食');
old_obj.bestfood.name3 = '巧克力';
console.log(old_obj);
console.log(new_obj);
</script>
</body>
</html>