Underscore: 深度扩展和深度复制

创建于 2011-03-25  ·  28评论  ·  资料来源: jashkenas/underscore

功能要求:
是否可以在_.extend()_.copy()上有一个布尔参数来使它们更深,或者有单独的深度方法?

enhancement wontfix

最有用的评论

_.deepClone = function(obj) {
      return (!obj || (typeof obj !== 'object'))?obj:
          (_.isString(obj))?String.prototype.slice.call(obj):
          (_.isDate(obj))?new Date(obj.valueOf()):
          (_.isFunction(obj.clone))?obj.clone():
          (_.isArray(obj)) ? _.map(obj, function(t){return _.deepClone(t)}):
          _.mapObject(obj, function(val, key) {return _.deepClone(val)});
  };

所有28条评论

恐怕 JavaScript 中的深拷贝操作没有真正好的语义。 现有的实现有各种错误,比如改变嵌套数组,以及无法复制嵌套的 Date 对象。 如果您想提出此功能,则必须附带一个防弹实现。

诚然,深拷贝在 javascript 中绝对是一团糟。 必须在某些时候说好处大于坏处,对吗? 让人们不断编写无法按预期工作的糟糕的深拷贝方法比使用经过仔细编码但有充分记录的限制的深拷贝方法更糟糕。 例如,这家伙的解决方案很棒: http ://stackoverflow.com/questions/728360/copying-an-object-in-javascript/728694#728694

他涵盖了大部分基础,并记录了它无法正确处理的内容。 他假设对象只包含这些类型:Object、Array、Date、String、Number 和 Boolean,并且他假设任何对象或数组都只包含相同的类型。

是的——即使是那个家伙的解决方案也确实不够好。 如果我们不能正确实现它,我们就不应该实现它。

但是,用户推出自己的、甚至可能更糟糕的实施真的更好吗?

不——用户最好不要在 JavaScript 中进行深度复制。 您通常可以找到一种方法来完成相同的目的,而不必拥有强大的深度复制功能......通过提前了解您想要复制的对象的结构。

啊,所以您是在建议用户使用必要的值来为对象的新实例添加水合物? 我理解了。

Jquery.extend 有很深的选择。

深度选项 +1,无论实施有多困难。

为此+2 - 实施并不难,这是一个原则问题(即不实施不完美的解决方案)。 但是,如上所述,它在 jQuery 库中使用,并且在除少数边缘情况外的所有情况下都非常有用。 如果能在这样的轻量级库中使用它会很好。

@kmalakoff写了一个实现,你应该给他一些反馈! :)

您甚至可以将我原来的 _.cloneToDepth 与 _clone 合并,然后添加一个深度参数....

  // Create a duplicate of a container of objects to any zero-indexed depth.
  _.cloneToDepth = _.clone = function(obj, depth) {
    if (typeof obj !== 'object') return obj;
    var clone = _.isArray(obj) ? obj.slice() : _.extend({}, obj);
    if (!_.isUndefined(depth) && (depth > 0)) {
      for (var key in clone) {
        clone[key] = _.clone(clone[key], depth-1);
      }
    }
    return clone;
  };

此外,我编写了 _.own 和 _.disown ,它们引入了所有权约定(保留/释放或克隆/销毁对)。 它只向下递归一级,但我想可以为总递归添加一个选项(我想看看一个用例!)。

  _.own = function(obj, options) {
    if (!obj || (typeof(obj)!='object')) return obj;
    options || (options = {});
    if (_.isArray(obj)) {
      if (options.share_collection) { _.each(obj, function(value) { _.own(value, {prefer_clone: options.prefer_clone}); }); return obj; }
      else { var a_clone =  []; _.each(obj, function(value) { a_clone.push(_.own(value, {prefer_clone: options.prefer_clone})); }); return a_clone; }
    }
    else if (options.properties) {
      if (options.share_collection) { _.each(obj, function(value, key) { _.own(value, {prefer_clone: options.prefer_clone}); }); return obj; }
      else { var o_clone = {}; _.each(obj, function(value, key) { o_clone[key] = _.own(value, {prefer_clone: options.prefer_clone}); }); return o_clone; }
    }
    else if (obj.retain) {
      if (options.prefer_clone && obj.clone) return obj.clone();
      else obj.retain();
    }
    else if (obj.clone) return obj.clone();
    return obj;
  };

  _.disown = function(obj, options) {
    if (!obj || (typeof(obj)!='object')) return obj;
    options || (options = {});
    if (_.isArray(obj)) {
      if (options.clear_values) { _.each(obj, function(value, index) { _.disown(value); obj[index]=null; }); return obj; }
      else {
        _.each(obj, function(value) { _.disown(value); });
        obj.length=0; return obj;
      }
    }
    else if (options.properties) {
      if (options.clear_values) { _.each(obj, function(value, key) { _.disown(value); obj[key]=null; }); return obj; }
      else {
        _.each(obj, function(value) { _.disown(value); });
        for(key in obj) { delete obj[key]; }
        return obj;
      }
    }
    else if (obj.release) obj.release();
    else if (obj.destroy) obj.destroy();
    return obj;
  };

如果你想要一个通用的、可扩展的克隆:

  // Create a duplicate of all objects to any zero-indexed depth.
  _.deepClone = function(obj, depth) {
    if (typeof obj !== 'object') return obj;
    if (_.isString(obj)) return obj.splice();
    if (_.isDate(obj)) return new Date(obj.getTime());
    if (_.isFunction(obj.clone)) return obj.clone();
    var clone = _.isArray(obj) ? obj.slice() : _.extend({}, obj);
    if (!_.isUndefined(depth) && (depth > 0)) {
      for (var key in clone) {
        clone[key] = _.deepClone(clone[key], depth-1);
      }
    }
    return clone;
  };

在给出深拷贝实际上是最佳解决方案的用例之前,我不相信这是一个好主意。 我认为很难找到一个。

我对昨天的回复并不完全满意(午夜后不应该写代码)......我想出了两个版本( _.cloneToDepth基本上只是克隆容器,保留对原始对象的引用和_.deepClone复制实例):

  // Create a duplicate of a container of objects to any zero-indexed depth.
  _.cloneToDepth = _.containerClone = _.clone = function(obj, depth) {
    if (!obj || (typeof obj !== 'object')) return obj;  // by value
    var clone;
    if (_.isArray(obj)) clone = Array.prototype.slice.call(obj);
    else if (obj.constructor!=={}.constructor) return obj; // by reference
    else clone = _.extend({}, obj);
    if (!_.isUndefined(depth) && (depth > 0)) {
      for (var key in clone) {
        clone[key] = _.clone(clone[key], depth-1);
      }
    }
    return clone;
  };

  // Create a duplicate of all objects to any zero-indexed depth.
  _.deepClone = function(obj, depth) {
    if (!obj || (typeof obj !== 'object')) return obj;  // by value
    else if (_.isString(obj)) return String.prototype.slice.call(obj);
    else if (_.isDate(obj)) return new Date(obj.valueOf());
    else if (_.isFunction(obj.clone)) return obj.clone();
    var clone;
    if (_.isArray(obj)) clone = Array.prototype.slice.call(obj);
    else if (obj.constructor!=={}.constructor) return obj; // by reference
    else clone = _.extend({}, obj);
    if (!_.isUndefined(depth) && (depth > 0)) {
      for (var key in clone) {
        clone[key] = _.deepClone(clone[key], depth-1);
      }
    }
    return clone;
  };

正如@michaelficarra指出的那样,用例可能不清楚。 就个人而言,我使用:

1) _.own / _.disown当我想共享具有复杂生命周期和/或所有权模型的对象时(如引用计数以正确处理清理)
2)当我有复杂的嵌套选项时,我使用_.cloneToDepth / _.containerClone (很少!)。
3)我从来不需要_.deepClone ,但假设它可以用作通用_.clone方法,如果您正在编写一个灵活支持类型的通用函数(例如,我不需要关心你传递给我的东西,但我会修改它并且不想对原件产生副作用 - 尽管字符串是一种特殊的不可变情况)。

我在这里提交了代码和测试: https ://github.com/kmalakoff/underscore-awesomer/commit/0cf6008f16ad6e6daf60caf456021693ef33fec5

注意:那些坚持为具有可预测语义的简单对象寻找单行、不完整的深度克隆的人可以只是JSON.parse(JSON.stringify(object))

@adamhooper这种方法是否倾向于丢失属性或其他东西? 为什么不完整?

@diversario它不会复制对象原型,也不会复制函数。 这以递归方式应用——因此它也不会正确复制嵌套对象的原型或函数。

特别是:它不会正确复制对象树中的任何日期。 而且,如果您想修复它以使用 Dates,那么您只是在解决更大问题的一个小症状。

啊对。 我主要用它来打破对“模板”对象之类的引用,所以我没有遇到过类似的事情。 但我认为需要真正的深拷贝。

我已经把 Kurt Milam 的 deepExtend mixin 变成了一个 npm 包。

https://github.com/pygy/undescoreDeepExtend/

@michaelficarra ,请解释深度复制如何不是克隆通用树结构的好解决方案?

对于所有尝试使用@adamhooper单行深拷贝的人,请注意它不适用于日期
JSON.parse(JSON.stringify(对象))
实际上它将任何对象 Date 转换为字符串

-1 表示深拷贝。 为配置对象使用原型链总是比嵌套选项更适合您的 api。

实际上深拷贝有一个地方,但它应该与类型检查相吻合,所以在我看来这是一个非常具体的用例,而不是通用目的。 更适合 JSON 模式库或配置加载器。 不是 javascript 工具带。

在大多数情况下_.extend({}, obj1, { prop1: 1, prop2: 2 })是我真正需要做的:

  • 给了我一个新对象
  • 不修改我的源对象和
  • 不是深拷贝
_.deepClone = function(obj) {
      return (!obj || (typeof obj !== 'object'))?obj:
          (_.isString(obj))?String.prototype.slice.call(obj):
          (_.isDate(obj))?new Date(obj.valueOf()):
          (_.isFunction(obj.clone))?obj.clone():
          (_.isArray(obj)) ? _.map(obj, function(t){return _.deepClone(t)}):
          _.mapObject(obj, function(val, key) {return _.deepClone(val)});
  };

我们在工作环境中经常遇到一个可以从深度克隆中受益的用例。

有时,当复杂的 Web 应用程序的功能更新并扩展到新的范例时,您必须进行数据库迁移。 当必须执行诸如从数据生成哈希之类的操作时,当原始引用不被操纵时,我们数据的深层副本非常有用。 我不认为我们庞大的数据库中的单个文档是“扁平的”。 嵌套属性无处不在。

此外,有时您希望将对象的副本发送到 Web 应用程序中的不同服务,以便在决定如何处理它之前对其进行操作。 通过引用拥有嵌套属性会破坏拥有克隆对象的目的。 克隆对象的整个点,imo,是保持原始引用完好无损。 一旦您介绍了更深层次的参考资料,整个目的就没有实际意义了。

我确信有一个不错的方法可以解决这个问题,可以将对象导航到它的深度,然后从最深的层向上重建到根。

此页面是否有帮助?
0 / 5 - 0 等级

相关问题

ksullivan picture ksullivan  ·  9评论

chikamichi picture chikamichi  ·  8评论

umarfarooq125 picture umarfarooq125  ·  8评论

arypbatista picture arypbatista  ·  3评论

xiaoliwang picture xiaoliwang  ·  3评论