Go to comments

JavaScript 克隆

克隆


一个已有的对象obj,现在把对象obj克隆给一个空对象obj1

var obj = {
    name : 'abc',
    age : '123,',
    sex : 'female'
}

var obj1 = {}

/**
 * 写一个克隆方法clone(origin, target)
 * origin  Object  原始的对象
 * target  Object  模板对象
 * */ 
function clone(origin ,target){
    for(var prop in origin){         // 循环把origin上面所有的属性都拿出来
        target[prop] = origin[prop]; // 每一次循环拿出的属性名origin[prop],让"target[prop] = origin[prop]"。
    }
}

clone(obj ,obj1); // 克隆obj
console.log(obj1); // 打印obj1 {name: "abc", age: "123,", sex: "female"}

这个方法再兼容一下(容个错)防止用户不传target。

如果用户传一个空对象就用户传的,如果没传就新建一个空对象,换句话说不传第二个参数也行。

function clone(origin ,target){
    var target = target || {}; // 判断target如果传第二个参数就用传进来的,如果没传就自动生成一个空对象。
    for(var prop in origin){
        target[prop] = origin[prop];
    }
    return target;             // 最后把克隆完的反回去
}

var obj = {
    name : 'abc',
    age : '123,',
    sex : 'female'
}

var obj1 = clone(obj);
console.log(obj1); // {name: "abc", age: "123,", sex: "female"}

有一个问题,这个克隆是浅层克隆,浅层克隆原始值克隆完后是没问题的。

浅层克隆

function clone(origin ,target){
    var target = target || {};
    for(var prop in origin){
        target[prop] = origin[prop];
    }

    return target;
}

var obj = {
    name : 'abc',
    age : '123,',
    sex : 'female',
}

var obj1 = clone(obj)

console.log(obj);    // obj  {name: "abc", age: "123,", sex: "female"}
console.log(obj1);   // obj1 {name: "abc", age: "123,", sex: "female"} 

obj.name = 'bcd';          // 现在让obj的name值改成bcd
console.log(obj1.name);    // obj1的name没有变化还是abc,因为是原始值,原始值就是值的拷贝

原始值就是值的拷贝,现在也让值拷贝,但是让引用值拷贝。

function clone(origin ,target){
    var target = target || {};
    for(var prop in origin){
        target[prop] = origin[prop];
    }
    return target;
}

var obj = {
    name : 'abc',
    age : '123,',
    sex : 'female',
    car : ['visa','unionpay'] // 里面加一个数组
}

var obj1 = clone(obj);

console.log(obj1);       // car1 ["visa", "unionpay"]
obj1.car.push('master'); // obj1.car是引用值,push进去一个'master'进去

console.log(obj1.car);   // obj1.car肯定的会多一个 ["visa", "unionpay", "master"] 
console.log(obj.car);    // 但是obj.car也被变了 ["visa", "unionpay", "master"]

因为是引用值,拷贝过去的是一个引用,两个人指向的是同一个房间,你改我也改。

现在我们想实现"深度克隆","深度拷贝"的意思是,我拿的和你的属性是一模一样的,但是就不是一个人。

你有一个引用值car里面放的是数组,拷贝过来之后我也有car数组,但是拷贝到我这之后,我改你是不会被更改的,两个人用的是两个房间。

深层克隆

浅度克隆非常简单,就是把A拷贝给B。深度克隆和浅度克隆的区别,深度克隆克隆后,不论是引用值还是原始值,都是各自独立的。

深度克隆和浅度克隆一样,也需要两个参数origin, targe

function deepClone(origin, target){
    
}

对象obj作为原始对象,深度克隆,把obj拷贝给obj1。

var obj = {
    name : 'abc', 
    age : 123,
    card : ['vise', 'master'],      
    wife : {
        name : "lili",
        son : {

        }
    }
}

先考虑一下深度克隆的步骤:

1. 遍历对象,判断是不是原始值

// 1). 首先把obj所有的属性值都挨个遍历一遍。
// 2). 每次遍历的时候判断一下,当前值是原始值还是引用值。
// 3). 是原值直接拷贝,原始值之间到拷贝是栈内存的拷贝,直接复制值过去。
//    PS:引用值也是栈内存赋值,只不过复制的是一个地址,所以不行。

var obj1 = {
    name : 'abc', // 原值直接拷贝
    age : 123,    // 原值直接拷贝
}

2. card是引用值怎么办?

// 1). 判断card是不是原始值,不是原始值是引用值。
// 2). 引用值有两种,判断是数组还是对象
// 3). 如果是数组建立新数组,如果是对象建立新对象。

var obj1 = {
    name : 'abc', 
    age : 123, 
    card : [],    // car不原始值是引用值怎么办?判断是数组还是对象,是数组建立新数组、空数组
}

3. 建立完相应的数组或对象后怎么办?

// 1). 然后,建立一个新的引用值之后,把原始的引用值当做被拷贝对象,把新的空数组当做拷贝对象,再一次进行深度拷贝。
// 2). 这样判断的过程就循环了,重新从开始的第一部开始走了。
// 3). 先判断数组里面第一位是不是引用值,如果是原始值直接拷贝,如果是引用值看看是那种类型,是数组还是对象,到这就开始循环了。
// 4). 数组里面都是原始值,直接拷贝
   
var obj1 = {
    name : 'abc',
    age : 123, 
    card : [obj.car[0], obj.car[1]], // 把原始的引用值当做被拷贝对象,把新的空数组当做拷贝对象,再一次进行深度拷贝。
}                                    // 数组里面都是原始值,直接拷贝

然后,看下一个wife是对象

// 1). 先判定是引用值,然后判定是对象,判定后建立一个空对象{}
// 2). 完后,做类似的wife对象到{}空对象的拷贝。
// 3). 紧接着,判定过程又循环了,看看wife对象里面的每一个值是不是原始值,
// 4). 第一个name是不是原始值,是原始值直接拷贝。
// 5). 第二个son不是原始值,建立新的对象,
// 6). 再把对象obj.wife.son到obj1.wife.son新建空对象的拷贝过程循环一次。

var obj1 = {
    name : 'abc',
    age : 123, 
    card : [obj.card[0], obj.card[1]],
    wife : {}                           // 然后,看下一个wife是对象,建立一个空对象
}
                                   
var obj1 = {
    name : 'abc',
    age : 123, 
    card : [obj.card[0], obj.card[1]]],
    wife : { 
        name : "bcd",                   // 第一个name是原始值直接拷贝。
    }
}

var obj1 = {
    name : 'abc',
    age : 123, 
    card : [obj.card[0], obj.card[1]]],
    wife : { 
        name : "bcd",                  
        son : {}                       // 第二个son不是原始值,建立新的对象
    }
}

var obj1 = {
    name : 'abc',
    age : 123, 
    card : [obj.card[0], obj.card[1]]],
    wife : { 
        name : "bcd", 
        son : {
            name : "lili",           // 再把对象obj.wife.son到这个{}对象的拷贝过程循环一次。
        }                
    }
}

开始写代码:

主要来说就三步,三步之外就开始循环重复这三步了。

首先要便历对象,遍历对象用"for in"循环,"for in"除了遍历也能遍历数组。

// 数组没有属性名也能"for in"循环

var arr = ['a', 'b', 'c'];

for(var prop in arr){
    console.log(prop); // prop代表索引位,依次代表0,1,2...
}

数组也能"for in",因为数组也算特殊类型的对象。

原始值怎么判断?用typeof()判断,如果结果是object是引用值,如果结果不是,基本上是原始值。

console.log(typeof({})); // object

console.log(typeof([])); // object

var a = 'abc';
console.log(typeof(a)); // string

数组还是对象怎办判断?有三种方法instanceof、toString、constructor。

Object.prototype.toString.call({}); // [object Object]

Object.prototype.toString.call([]); // [object Array]

/***
判断是数组还是对象的三种方法instanceof、toString、constructor,用的时候这三种都行,建议大家用toString。因为instanceof、constructor有个小问题,这个问题基本上一辈子也不会遇到但是确实有。
这个问题是,js学到后期会有父子域的问题,也就是说一个页面里未必只有一个页面,可能还有个子页面,比如"<iframe src="http://baidu.com"></iframe>"有一个父子域。
子域里面的一个数组,放到父域里面的Array,"[] instanceof Array",会打印false,正常来说是true,但父子域里面是false。
只有toString没有父子域的问题。
***/

做预备工作

1). 先做一个容错"target = traget || {}",后期"或运算符"对值的应用基本上是这样。

2). 会用到"Objecte.prototype.toString()",每次写这么长太麻烦,把它引用出来,这个"toStr.call()"就代表引用了。用的时候toStr.call()就行了。

3). "Object.prototype.toString.call([])"得出的结果要经历比对,比如跟数组比对还是跟对象比对。跟数组比对出来的是"[object Array]",把"[object Array]"弄能字符串储存起来。

function deepClone(origin, target){
    var target = traget || {},
        toStr = Object.prototype.toString,
        arrStr = "[object Array]"; 
}

紧接着开始遍历

// 遍历对象origin

function deepClone(origin, target){
    var target = traget || {},
        toStr = Object.prototype.toString,
        arrStr = "[object Array]";

    for(var prop in origin){
        if(origin.hasOwnProperty(prop)){
        
        }
    }
}

第一步,判断是不是原始值

判断每一个值是不是原始值,判断之前但凡是"for in"循环,要跟一个"hasOwnProperty(prop)"判断一下,别把原型链上的东西拿下来。如果是true再进行下一步判断,如果不是true代表是原型上到属性,原型上的属性不拷贝。

判断是不是原始值,判断"typeof origin[prop]"(用方括号的形式)的结果,如果结果是"object"代表引用值,不是基本上是原始值,
是原始值直接赋值拷贝"target[prop] = origin[prop]",这一下就把原始值搞定了。null先不考虑最后再补充。

function deepClone(origin, target){
    var target = traget || {},
        toStr = Object.prototype.toString,
        arrStr = "[object Array]";

    for(var prop in origin){
        if(origin.hasOwnProperty(prop)){
            if(typeof origin[prop] == 'Object'){ // 判定是引用值
                // 引用值...
            }else{
                 target[prop] = origin[prop]; // 原始值直接搞定
            }
        }
    }
}

// PS: hasOwnProperty()尽量写上,因为deepClone写的是一个方法的抽象,因为不知道未来传进来的对象是什么样的,所以尽量把方法写全。这是克隆方法的一个抽象,方法都是抽象来的,方法就是要应用于任何的情况。
// 未来传进来的对象有可能就有原型,所以hasOwnProperty()尽量得写(为了避免拿对象原型链上的属性)。
// 原始值上还有包装类,在包装类上建东西也能拿下来。

接下来到引用值,第二步,第三步都是给引用值准备的。第二步判断是数组还是对象,第三步判断完建立相应的数组或对象。

已知"origin[prop]"已经是引用值了,判断是数组的引用值还是对象的引用值,怎么判断?用Object.prototype.toString一下已经写好了引用"toStr.call(origin[prop])"。
当返回值等于"[object Array]",是数组就建立一个"[]"空数组,是对象就建立一个空对象。在哪建立数组/对象?在"target[prop]"身上建立数组或对象。

function deepClone(origin, target){
    var target = traget || {},
        toStr = Object.prototype.toString,
        arrStr = "[object Array]";

    for(var prop in origin){
        if(origin.hasOwnProperty(prop)){
            if(typeof origin[prop] == 'object'){ 
                
                if(toStr.call(origin[prop]) == arrStr){ // "origin[prop]"已经是引用值了,判断是数组的引用值,还是对象的引用值
                    target[prop] = []; // 建立数组
                }else{
                    target[prop] = {}; // 建立对象
                }

            }else{
                target[prop] = origin[prop];
            }
        }
    }
}

第三部完事了,然后递归。

递归的过程,是把已有的原始数组当做拷贝对象,把新建立的空数组当做被拷贝对象。origin[prop],target[prop]再来一遍这样的循环

function deepClone(origin, target){
    var target = target || {},
        toStr = Object.prototype.toString,
        arrStr = "[object Array]";

    for(var prop in origin){
        if(origin.hasOwnProperty(prop)){
            if(typeof origin[prop] == 'object'){
                if(toStr.call(origin[prop]) == arrStr){
                    target[prop] = [];
                }else{
                    target[prop] = {};
                }
                deepClone(origin[prop], target[prop]); // 递归,origin[prop],target[prop]再来一遍这样的循环。
            }else{
                target[prop] = origin[prop]; // 递归出口,当origin[prop]是原始值,就是出口了。
            }
        }
    }
}

试一下深层拷贝

function deepClone(origin, target){
    var target = target || {},
        toStr = Object.prototype.toString,
        arrStr = "[object Array]";

    for(var prop in origin){
        if(origin.hasOwnProperty(prop)){
            if(typeof origin[prop] == 'object'){
                if(toStr.call(origin[prop]) == arrStr){
                    target[prop] = [];
                }else{
                    target[prop] = {};
                }
                deepClone(origin[prop], target[prop]);
            }else{
                target[prop] = origin[prop];
            }
        }
    }
}


var obj = {
    name : 'abc',
    age : 123,
    card : ['vise', 'master'],
    wife : {
        name : "bcd",
        son : {
            name : "aaa"
        }
    }
}

var obj1 = {}

deepClone(obj, obj1);

console.log(obj1); // {name: "abc", age: 123, card: Array(2), wife: {…}}
console.log(obj);  // {name: "abc", age: 123, card: Array(2), wife: {…}}

obj.card.push('abc');   // obj.card有两个值,再push一个abc进去

console.log(obj1.card); // obj1.card还是两个["vise", "master"],它两个互没影响,深层拷贝实现了。我的就是我的,你的就是你的,引用值也是你的就是你的,我的就是我的,完全分开了。

这就是深度拷贝的一个过程。

再完稍微完善一下,如果没传target,要把target返回出去。还有null的问题,在那判断?

function deepClone(origin, target){
    var target = target || {},
        toStr = Object.prototype.toString,
        arrStr = "[object Array]";

    for(var prop in origin){
        if(origin.hasOwnProperty(prop)){
            // null的问题在这判断。
            // 什么是引用值?不是null而且typof结果还是object。所以前面还要加一条typeof origin[prop] !== "null"绝对不等于"null",绝对不等于有隐式类型转换都不行。把后面两个条件叠加在一起。
            if(typeof origin[prop] !== "null" && typeof origin[prop] == 'object'){
                if(toStr.call(origin[prop]) == arrStr){
                    target[prop] = [];
                }else{
                    target[prop] = {};
                }
                deepClone(origin[prop], target[prop]);
            }else{
                target[prop] = origin[prop];
            }
        }
    }
    return target; // 如果没传target,要把target返回出去。
}


var obj = {
    name : 'abc',
    age : 123,
    card : ['vise', 'master'],
    wife : {
        name : "bcd",
        son : {
            name : "aaa"
        }
    }
}


var objCopy = deepClone(obj);

console.log(objCopy); // {name: "abc", age: 123, card: Array(2), wife: {…}}
console.log(obj);  // {name: "abc", age: 123, card: Array(2), wife: {…}}

obj.card.push('abc');   // obj.card有两个值,再push一个abc进去

console.log(objCopy.card); // objCopy.card还是两个["vise", "master"]



Leave a comment 0 Comments.

Leave a Reply

换一张