`
yiminghe
  • 浏览: 1431414 次
  • 性别: 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
分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics