underScore专题-浅克隆,自己实现深克隆

clone

underScore中暴露除了一个方法clone:

    // Create a (shallow-cloned) duplicate of an object.
    function clone(obj) {
        if (!isObject(obj)) return obj;
        return isArray(obj) ? obj.slice() : extend({}, obj);
    }
    // Is a given variable an object?
    function isObject(obj) {
        var type = typeof obj;
        return type === 'function' || type === 'object' && !!obj;
    }

如果是数组,直接slice()一下,返回一个新的数组,跟原数组内容相同。两个数组的指针不同,这样就实现了一次简单深拷贝。

    var arr = [3, 4, 3, 4]
    var newArr = arr.slice(arr)
    console.log(newArr) // [3, 4, 3, 4]
    console.log(newArr === arr) // false

如果是obj,调用了extend方法,这个方法在underScore专题-源码分析迭代器中讲到过,就是实现了一个浅拷贝。

 extend({}, obj);
    var obj = { name: '麦乐', params: { code: 300 }, list: [2, 3, [3]] }
    var cloneObj = _.clone(obj)
    console.log(cloneObj)
    console.log(cloneObj === obj) // false
    console.log(cloneObj.params === obj.params) // true
    cloneObj.params.code = 200;
    console.log(obj.params) // 200

cloneObj的打印结果: 

副本的cloneObj跟obj不相等,指针不再指向同一个,但是,如果属性值是引用数据类型,拿到的就是对象的引用:

    console.log(cloneObj.params === obj.params) // true

对cloneObj.params对象属性的修改会直接影响到原对象。这样的clone不属于真正的clone,我们要做到跟原对象完全解藕,这样才是干净的clone。

下面参考jQuery的extend方法,实现一下深度clone:

辅助函数:判断是不是数组,这里用一个柯里化函数tagTester,先固定一个参数,生成一个函数,再接受剩下的参数,实现参数复用。

柯里化函数:Currying(果然是满满的英译中的既视感),是把接受多个参数的函数变换成接受一个单一参数(最初函数的第一个参数)的函数,并且返回接受余下的参数而且返回结果的新函数的技术。

 // Internal function for creating a toString-based type tester.
    function tagTester(name) {
        return function(obj) {
            return toString.call(obj) === '[object ' + name + ']';
        };
    }

    // Is a given value an array?
    // Delegates to ECMA5's native Array.isArray
    var isArray = nativeIsArray || tagTester('Array');

要实现深度拷贝,其实很简单,就是判断一下属性值是不是对象,如果是,递归处理,如果不是,直接赋值:

    function deepClone(obj) {
        if (!isObject(obj)) return obj;
        var target = {}, copy = ''
        for (var key in obj) {
     
            isObject(obj[key]) ? copy = deepClone(obj[key]) : copy = obj[key]
           
            target[key] = copy
        }
        return target;
    }

字面意思理解就这么简单,就能实现深度拷贝,跟原对象解藕,但是这样写会不会有问题?验证一下:


    function deepClones(obj) {
        if (!isObject(obj)) return obj;
        var target = {}, copy = ''
        for (var key in obj) {
            isObject(obj[key]) ? copy = deepClone(obj[key]) : copy = obj[key]
            target[key] = copy
        }
        return target;
    }
    var obj = { name: '麦乐', params: { code: 300, list: [4, 3, 2] }, list: [2, 3, [3]] }
    var cloneObj = deepClones(obj)
    console.log(cloneObj)
    console.log(cloneObj === obj) // false
    console.log(cloneObj.params === obj.params) // false
    console.log(cloneObj.params.list === obj.params.list) // false

不管嵌套多么深,引用类型指针都不相同,也就是实现了完全解藕。

但是,看cloneObj的打印结果:

list原本是数组,却被拷贝成了对象,内部的数组还保持这原来数组,发现了漏洞, isObject是判断数据是不是对象,包裹数组,对象和函数, for in 循环的即有数组也有对象,而拷贝的过程中,并不知道到底是对象还是数组,所以这里就出现了问题:

isObject(obj[key]) ? copy = deepClone(obj[key]) : copy = obj[key]

为了解决上面的漏洞,这样来实现克隆函数:

    function deepClone(obj) {
        if (!isObject(obj)) return obj;
        var target = {}, copy = ''
        for (var key in obj) {
            var source = obj[key];
            // 这个拷贝的过程,会将[3,2] -> {0:3,1:2}这样的对象
            isObject(source) ? copy = deepClone(source) : copy = source
            if (isArray(source) && !copy.length) {
                // 把copy,也就是{0:3,1:2}转换为类数组
                // 类数组是带有length的对象,并且key 跟数组的下标一致
                copy.length = source.length;
                // 把类数组转换为数组
                copy = Array.prototype.slice.call(copy)
            }
            target[key] = copy
        }
        return target;
    }

原对象中的值如果是数组并且拷贝过来后没有length,也是就是拷贝成了对象,就增加length属性,变成类数组,再转化为数组。

如果说值是一个函数:

    var obj = { name: '麦乐', params: { code: function() { }, list: [4, 3, 2] }, list: [2, 3, [3]] }

好像并没有拷贝出来,这是因为isObject函数,把函数也列入来里面。

    function isObject(obj) {
        var type = typeof obj;
        return type === 'function' || type === 'object' && !!obj;
    }

如果把调整一下这个函数:

    function isObject(obj) {
        var type = typeof obj;
        return type === 'object' && !!obj;
    }

 

    var obj = { name: '麦乐', params: { code: function() { }, list: [4, 3, 2] }, list: [2, 3, [3]] }
    var cloneObj = deepClone(obj)
    console.log(cloneObj)
    console.log(cloneObj === obj) // false
    console.log(cloneObj.params === obj.params) // false
    console.log(cloneObj.params.list === obj.params.list) //false
    console.log(cloneObj.params.code === obj.params.code) // true

函数的引用指向的是同一个。这里就先不考虑函数了。只考虑是对象,数组,非null

    function isObject(obj) {
        var type = typeof obj;
        return type === 'object' && !!obj;
    }

当然这只是解决问题的一种方式,稍后会一起来了解一下jQuery中的实现方式。

上面问题可以换一种更简单的方式来解决:

    function deepCopy(obj) {
        let target = Array.isArray(obj) ? [] : {}
        for(let key in obj) {
            const sourceV = obj[key]
            if(sourceV && typeof sourceV === 'object') {
                target[key] = deepCopy(sourceV)
            } else {
                target[key] = sourceV;
            }
            
        }
        return target
    }

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值