深度克隆 Backbone.js 模型

8
我正在使用jquery和backbone.js开发并遇到了这样的情况:需要复制模型,但需要对它们进行深层复制,以便复制之间不存在引用关系。模型可以将其他模型作为属性。模型可以将匿名函数作为属性。
因此,我正在尝试创建一个算法来深度克隆大部分backbone模型。我期望在此复制中应该删除所有绑定(对于新实例),因此我不担心保留它们。
目标:
- 能够复制所有简单变量(字符串、整数、浮点数等)并将其存储到新模型中,名称相同。 - 已完成,使用toJSON创建一个新的JSON对象,可以传递给set()。此对象仅包含简单属性,即不包括分配给函数或其他模型的属性。 - 能够复制一些变量被分配给的匿名函数,而不预先知道函数/属性名称。 - 如果我知道赋给函数的属性名称,我可以复制它。但如果模型是新的或未知的,则没有那些信息。 - 如果一个属性是另一个backbone模型,则对该属性递归调用深层复制算法。 - 无法使用原生backbone方法检查属性是否为backbone模型,正在寻找解决方法。
以下是我目前所拥有的简化版本:
``` /** * Performs a deep copy of a backbone.js model * All bindings for the copy are lost * @param orgModel - the original model to copy */ function deepCopyModel(orgModel) { var dupModel = Backbone.Model.extend({});
var orgAttributes= orgModel.toJSON();
var keepAttr=_.keys(orgAttributes); //remove any special cases keepAttr=_.without( keepAttr , 'specialCase1', 'specialCase2' ); //or keepAttr=_.difference(keepAttr, ['specialCase1', 'specialCase2'] );
//remove undefined values keepAttr=_.filter(keepAttr,function(key) { return ( typeof(attributes[key])!="undefined" ); }); //grab the resulting list of attributes after filtering var result=_.pick(attributes,keepAttr); //assign attributes to the copy using set dupModel.set(result);
//TODO: Implement deep copy of functions
//TODO: Implement deep copy of inner models
return dupModel; } ```
非常感谢您提供的任何帮助或见解。谢谢!

1
出于好奇,为什么您需要深度复制函数 - WickyNilliams
你无法真正地深度复制函数:闭包的保存状态本质上是不透明的。例如:function(y) { var x=y; return function() { x++; console.log(x); }; } - tucuxi
嗯...下划线的extend方法将从一个对象复制静态属性、方法和原型上的所有内容到另一个对象中。你可以在定义时或运行时执行此操作。但我也很好奇为什么你想这样做,因为本质上你所描述的是继承。 - Brendan Delumpa
好的,我想我误解了复制函数的含义。只要新模型可以访问与旧模型相同的函数,并且不会在两者之间产生变量冲突,那么我就没问题了。 - Daniel Gradinjan
好的,问题是我需要拥有同一对象的两个实例。一个绑定到用户界面,另一个用于在后台处理。我不希望对这个后台模型所做的任何更改在进程完成之前反映到用户界面上。有时甚至连这个反应都不需要。需要两个完全不同的实例,只是包含相同的信息。 - Daniel Gradinjan
@DanielGradinjan,我的回答如下所示,展示了您如何拥有两个独立的对象(其中一个是从另一个生成的),引用相同的函数而不会相互影响! - WickyNilliams
2个回答

11

jQuery的extend方法允许你简单地将一个对象的属性复制到另一个对象中。

下面是一个人为制造的、但具有说明性的例子。它甚至展示了为什么你不需要进行“深度”复制函数!

var someObj = {
    a : "a",
    b : 12345,
    c : {
        d : "d",
        e : "e"
    },
    f : function() {
        alert(this.a);
    }
};

//copy from original to new empty object
var deepCopy = $.extend(true, {}, someObj);

deepCopy.a = "deepCopy.a";
deepCopy.c.d = "deepCopy.c.d";

alert("someObj is not affected when deep copying: " + someObj.c.d);
alert("deepCopy is entirely distinct when deep copying: " + deepCopy.c.d);

deepCopy.f();    
someObj.f();

这里有一个方便的jsfiddle链接:http://jsfiddle.net/S6p3F/3/

运行此代码后,您会发现someObjdeepCopy在结构上是相同但是不同的对象。

正如您所见,不需要深度复制函数,因为this引用与应用函数的对象绑定在一起。这是因为在JavaScript中,调用函数deepCopy.f()在功能上等同于deepCopy.f.call(deepCopy)。以下是更具说明性的示例:

function someFunction() {
    alert(this.someProperty);
}

var a = {
        someProperty: "a's property"
    },
    b = {
        someProperty: "b's property"
    };

someFunction.call(a);
someFunction.call(b);

这里有一个演示: http://jsfiddle.net/S6p3F/2/


2
如果您将true作为第一个参数提供给extend,它确实会执行深拷贝。示例已更新以反映此更改。至于闭包等等,任何对JS有合理了解的人都会理解这一点。我的观点不是它在所有情况下都能起作用,而是复制函数引用通常就足够了。 - WickyNilliams
我以前在另一个答案中评论了jQuery的extend功能。在复制对象之前,jQuery会进行一些测试来判断对象的类型。我对该答案表示怀疑。请搜索jQuery源代码中以下代码行: if ( deep && copy && ( jQuery.isPlainObject(copy) || (copyIsArray = jQuery.isArray(copy)) ) ) { - JayC
你打算如何处理克隆数组或backbone集合?浅拷贝(extend)只会改变基础引用,而不会改变数组元素的引用。 - Axle
Backbone 使用了 Underscore 库,它包含自己的 extend() 函数。Backbone 模型已经使用了这个函数。 - mtsr
1
我在使用Backbone模型时遇到了无限循环的问题。 - Christopher Masser
1
@ChristopherMasser 可能你的模型存在循环引用。例如,A 引用了 B,B 又引用了 A。你可以尝试使用浅拷贝或其他方法来解决。 - WickyNilliams

4

如果你正在使用Lo-Dash作为Underscore的替代品,你也可以使用_.cloneDeep

var newModel = new MyModel(_.cloneDeep(oldModel.toJSON());

我认为这对于嵌套在旧模型内部的模型和集合不会按预期工作。你最终将得到一个作为属性的 attributes 对象,等等。 - Adam Fraser

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接