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

彼はほとんどの基地をカバーし、正しく処理できないものを文書化します。 彼は、オブジェクトに次のタイプ(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を作成しました。 1レベル下で再帰するだけですが、完全再帰のオプションが追加される可能性があると思います(ユースケースを見たいです!)。

  _.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;
  };

ディープコピーが実際に最良の解決策であるユースケースが示されるまで、これが良い考えであると私は確信していません。 なかなか見つからないと思います。

昨日の応答に完全に満足していませんでした(深夜以降はコードを記述しないでください)... 2つのバージョンを考え出しました( _.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

注:予測可能なセマンティクスを持つ単純なオブジェクトの1行の不完全なディープクローンを探している人は、 JSON.parse(JSON.stringify(object))だけで済みます。

@adamhooperこのメソッドはプロパティなどを失う傾向がありますか? なぜ不完全なのですか?

@diversarioオブジェクトのプロトタイプはコピー

特に、オブジェクトツリーの日付は正しくコピーされません。 また、Datesで機能するように修正したい場合は、はるかに大きな問題の小さな症状に対処しているだけです。

そうそう。 私は主に「テンプレート」オブジェクトなどへの参照を壊すために使用するので、そのようなものに遭遇したことはありません。 しかし、私は本当の深いコピーの必要性を理解しています。

KurtMilamのdeepExtendミックスインをnpmパッケージに変えました。

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

@michaelficarra 、一般的なツリー構造のクローンを作成するためのディープコピーが適切なソリューションではないことを説明してください。

@adamhooperワンライナーディープコピーを使用しようとしている
JSON.parse(JSON.stringify(object))
実際、オブジェクトの日付を文字列に変換します

ディープコピーの場合は-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は、元の参照をそのまま残すことです。 より深いレベルのリファレンスを紹介するとすぐに、全体の目的は議論の余地があります。

オブジェクトをその深さまでナビゲートし、最深層からルートまで再構築できる、これを実現するための適切な方法があると確信しています。

Lodashにはそれがあります-https://lodash.com/docs/4.17.5#cloneDeep

このページは役に立ちましたか?
0 / 5 - 0 評価