`
yiminghe
  • 浏览: 1460296 次
  • 性别: Icon_minigender_1
  • 来自: 上海
社区版块
存档分类
最新评论

循环引用下的深度克隆

 
阅读更多

深度复制和浅度复制 是当初初学 c 遇到的第一批问题,似乎使不少人困惑,而类 c 的 javascript 也同样存在这个问题.

 

 

第一版:

 

javascript 中引用类型(Object.prototype.toString.call(object))有 : Array 以及 Object , Date , RegExp ,Number, Function,Boolean .而可以修改自身的包括:

 

Array : 可修改自身单个元素

 

Object : 可修改自身单个属性

 

Date : 可修改自身日期,年份等

 

RegExp : 可修改 lastIndex

 

而对于基本类型的包装类型如:new Boolean() ,new Number() 虽然没有方法改变自身值,但是可能在上面附加数据,所以最好还是考虑下。

 

 

然后细心点进行深度复制:

 

function clone(o) {
    var ret = 0,
        isPlainObject, isArray;
    var constructor = o.constructor;


    // array or plain object
    if (((isArray = S.isArray(o)) || isPlainObject = S.isPlainObject(o))) {

        // 先把对象建立起来
        if (isArray) {
            ret = [];
        } else if (isPlainObject) {
            ret = {};
        }

        // clone it
        if (isArray) {
            for (var i = 0; i < o.length; i++) {
                ret[i] = S.clone(o[i]);
            }
        } else if (isPlainObject) {
            for (k in o) {
                if (o.hasOwnProperty(k)) {
                    ret[k] = S.clone(o[k]);
                }
            }
        }
    } else if (typeof o=="object"&&S.inArray(constructor, [Boolean, String, Number, Date, RegExp])) {
        ret = new constructor(o.valueOf());
    }

    return ret;
}
 

 

第二版:

 

上一版虽然考虑了引用类型,但是对于一种特殊情况却会引起巨大的麻烦:循环引用时的无穷递归。例如以下数据类型:

 

var son={name:"x"},father:{name:"y"};
father.son=son;
son.father=father;

var newSon=S.clone(son);
 

虽然这种情况很少见,甚至不推荐。但是场景确实会存在,比如 dom 树节点就是个很好的例子.

 

解决:

 

首先要防止死循环,最常见的做法即是做标记,如果一个源已经被克隆过了,那么只需返回对应的克隆对象即可。

 

随后就要清除先前的标记了,又是一个问题:怎么清除?从头开始清除?那么真陷入了死循环。为了避免再次死循环就需要在第一步做标记时,把做标记的元素存起来,当最后克隆完毕,再将标记统一清除:

 

 

var CLONE_MARKER = '__cloned';
function clone(o) {
    var marked = {},
        ret = cloneInternal(o, marked);
    S.each(marked, function(v) {
        // 清理在源对象上做的标记
        v = v.o;
        if (v[CLONE_MARKER]) {
            try {
                delete v[CLONE_MARKER];
            } catch (e) {
                S.log(e);
                v[CLONE_MARKER] = undefined;
            }
        }
    });
    marked = undefined;
    return ret;
}
function cloneInternal(o, f, marked) {
        var ret = o, isArray, k, stamp;
        // 引用类型要先记录
        if (o &&
            ((isArray = S.isArray(o)) ||
                S.isPlainObject(o) ||
                S.isDate(o) ||
                S.isRegExp(o)
                )) {
            if (o[CLONE_MARKER]) {
                // 对应的克隆后对象
                return marked[o[CLONE_MARKER]].r;
            }
            // 做标记
            o[CLONE_MARKER] = (stamp = S.guid());

            // 先把对象建立起来
            if (isArray) {
                ret = f ? S.filter(o, f) : o.concat();
            } else if (S.isDate(o)) {
                ret = new Date(+o);
            } else if (S.isRegExp(o)) {
                ret = new RegExp(o);
            } else {
                ret = {};
            }

            // 存储源对象以及克隆后的对象
            marked[stamp] = {r:ret,o:o};
        }


        // array or plain object need to be copied recursively
        if (o && (isArray || S.isPlainObject(o))) {
            // clone it
            if (isArray) {
                for (var i = 0; i < ret.length; i++) {
                    ret[i] = cloneInternal(ret[i], f, marked);
                }
            } else {
                for (k in o) {
                    if (k !== CLONE_MARKER &&
                        o.hasOwnProperty(k) &&
                        (!f || (f.call(o, o[k], k, o) !== false))) {
                        ret[k] = cloneInternal(o[k], f, marked);
                    }
                }
            }
        }

        return ret;
    }
 

可以找个复杂的例子验证下:

 

var t7 = [],
           
            t8 = {x:1,z:t7},
            t9 = {y:1,z:t7};
        t7.push(t8, t9);
 

画个图就是:

 

那么 clone=S.clone(t7) 的结果应该和 t7 内容一样并且包含关系完全相同即:

 

 

 

 

 

不足:

 

该算法只适用于配置参数等简单数据类型克隆,对于具备复杂原型链的自定义对象尚不能很好支持,或许可以通过

 

ret=new o.constructor()
 

来生成对应类型对象,但是由于执行了构造器或存在副作用.

 

 

Refer:

 

 

原来已经有规范了,不过如果出现 HTMLNode function 就报错的做法不妥?:

 

结构化数据克隆html5规范

 

 

  • 大小: 20.9 KB
  • 大小: 21.3 KB
分享到:
评论

相关推荐

    java深度克隆

    2. **手工编写方式**:对于复杂对象或者包含大量循环引用的对象,可以通过重写对象中的`clone()`方法并手动处理其中的引用类型成员变量来实现深度克隆。这种方式更加灵活,但编写起来相对复杂且容易出错。 #### 三...

    深度克隆的事例代码

    这种方法简单易用,但不适用于包含函数或循环引用的对象,因为它不能处理函数和会丢失原型链信息。 ### Python中的深度克隆 Python的`copy`模块提供了`deepcopy()`函数,可以方便地实现深度克隆: ```python ...

    JavaScript浅层克隆与深度克隆示例详解

    此外,还可以利用JSON序列化和反序列化实现深度克隆,但这种方法有一些限制,例如不能处理函数和循环引用。 ```javascript function deepClone(origin, target, hash = new WeakMap()) { if (origin === null) ...

    一个用于对象深度克隆的同构和可配置javascript函数

    4. **性能优化**:对于大型或循环引用的对象,深度克隆函数需要有良好的性能表现,避免无限递归和内存消耗过大。 5. **可配置性**:允许设置参数来定制克隆行为,例如忽略某些属性、处理特定类型的数据等。 总之,...

    JS对象深度克隆实例分析

    需要注意的是,深度克隆并不是JavaScript的标准功能,而是通过各种技术手段实现的,因此在处理复杂数据结构时可能会遇到问题,如循环引用、函数、日期、正则表达式等非标准JSON格式的对象。此外,性能也是一个考虑...

    object-clone:克隆对象支持循环引用

    深度克隆对象,支持循环引用和属性描述符 var clone = require ( '@dmail/object-clone' ) ; var a = { name : 'a' } ; var b = clone ( a ) ; b . name ; // 'a' // of course b != a 它克隆得很深 var a = { ...

    JS对象的深度克隆方法示例

    这样可以实现深度克隆,但需要注意的是,这种方法不适用于包含函数、循环引用、`undefined`等非JSON兼容类型的对象。 ```javascript var scheduleClone2 = JSON.parse(JSON.stringify(schedule)); ``` 虽然这种...

    同构和可配置的javascript实用程序,用于支持循环引用的对象深度克隆。-JavaScript开发

    omn​​iclone用于对象深度克隆的同构和可配置的javascript函数。 omn​​iclone(来源[,配置,[,访问者]]); 例如:const obj = {foo:{bar:'baz'}}; const obj2 = omniclone(obj); obj2; // {omniclone用于...

    一行代码实现纯数据json对象的深度克隆实现思路

    需要注意的是,这种方法仅适用于"纯数据"的JSON对象,即对象中没有包含函数、循环引用或其他非JSON兼容类型。如果对象中包含函数、日期对象、正则表达式或者其他不能直接转换为JSON的值,`JSON.stringify()`会忽略...

    nanoclone只有145B实现深度克隆JavaScript对象

    标题提到的"nanoclone"是一个极简的JavaScript库,其核心代码只有145字节,但能实现深度克隆功能。这在追求轻量级解决方案的场景下显得尤为宝贵。 在JavaScript中,有多种实现深拷贝的方法。例如,可以使用JSON的...

    JavaScript对象之深度克隆介绍

    例如,当你想要创建一个用户配置对象的副本,以便在不改变用户原始设置的情况下进行操作,或者在处理复杂数据结构时,确保不会污染原始数据,深度克隆就显得尤为重要。 在提供的代码示例中,展示了如何手动实现一个...

    javascript克隆对象深度介绍

    JavaScript中的对象克隆,主要是指复制一个对象,得到一个新的对象,这个新对象具有与原对象...对于复杂的对象或数组,尤其是在对象中存在嵌套循环引用的情况下,可能需要专门的库或更为复杂的递归函数来实现深度克隆。

    js代码-javascript深度克隆

    JavaScript深度克隆是一种在编程中复制对象或数组的方式,它不仅复制了对象的表面属性,还复制了嵌套的对象和数组。在JavaScript中,浅拷贝(shallow copy)只复制对象的引用,而深度克隆(deep clone)会创建一个...

    C# List引用类型克隆的3种方法

    这种方法简单快捷,但需要引入Newtonsoft.Json库,且不能处理非序列化的成员或循环引用。 ```csharp public static List&lt;T&gt; Clone(this List&lt;T&gt; list) where T : new() { var str = JsonConvert.SerializeObject...

    前端项目-clone.zip

    但是,这种方法不能处理函数、循环引用以及Date、RegExp等特殊类型的对象。 2. 循环遍历:通过递归地遍历对象的所有属性,创建一个新的对象并逐个复制属性。这可以处理更复杂的情况,包括嵌套的对象和数组,但性能...

    前端开源库-is-circular

    - 在深度克隆或拷贝对象时,确保不会因为循环引用导致栈溢出或内存消耗过大。 - 在进行JSON.stringify()操作前,检查并打断循环引用,防止报错。 - 在数据传输或存储前,确保数据结构无循环引用,提高数据处理...

Global site tag (gtag.js) - Google Analytics