深度复制和浅度复制 是当初初学 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 就报错的做法不妥?:
JavaScript对象深度复制与循环引用解决方案

352

被折叠的 条评论
为什么被折叠?



