Underscore: Perluas Jauh dan Salin Dalam

Dibuat pada 25 Mar 2011  ·  28Komentar  ·  Sumber: jashkenas/underscore

Permintaan fitur:
Apakah mungkin untuk memiliki parameter boolean pada _.extend() dan _.copy() untuk membuatnya dalam, atau memiliki metode dalam yang terpisah?

enhancement wontfix

Komentar yang paling membantu

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

Semua 28 komentar

Saya khawatir tidak ada semantik yang benar-benar bagus untuk operasi penyalinan mendalam di JavaScript. Implementasi yang ada memiliki berbagai bug, seperti mengubah array bersarang, dan gagal menyalin objek Tanggal bersarang. Jika Anda ingin mengusulkan fitur ini, Anda harus melengkapinya dengan implementasi antipeluru.

Benar, salinan yang dalam pasti berantakan di javascript. Pasti ada titik di mana Anda mengatakan bahwa manfaatnya lebih besar daripada kerugiannya, bukan? Membuat orang terus-menerus menulis metode salinan dalam yang payah yang tidak bekerja seperti yang diharapkan lebih buruk daripada menggunakan metode penyalinan dalam yang telah dikodekan dengan hati-hati, tetapi memiliki keterbatasan yang terdokumentasi dengan baik. Misalnya, solusi orang ini hebat: http://stackoverflow.com/questions/728360/copying-an-object-in-javascript/728694#728694

Dia mencakup sebagian besar pangkalan, dan mendokumentasikan apa yang tidak ditangani dengan benar. Dia menganggap objek hanya berisi jenis ini: Objek, Array, Tanggal, String, Angka, dan Boolean, dan dia berasumsi bahwa objek atau array apa pun hanya akan berisi hal yang sama.

Yap -- dan bahkan solusi orang itu tidak cukup baik. Jika kita tidak bisa mengimplementasikannya dengan benar, kita seharusnya tidak mengimplementasikannya.

Tetapi apakah benar-benar lebih baik bagi pengguna untuk menggulung implementasi mereka sendiri, bahkan mungkin lebih rusak?

Tidak -- lebih baik bagi pengguna untuk tidak menyalin dalam JavaScript. Anda biasanya dapat menemukan cara untuk mencapai tujuan yang sama tanpa harus memiliki fungsi penyalinan dalam yang kuat ... dengan mengetahui struktur objek yang ingin Anda salin terlebih dahulu.

Ah, jadi Anda menyarankan pengguna menghidrasi objek baru dengan nilai yang diperlukan? Saya bisa melihat itu.

Jquery.extend memiliki opsi yang dalam.

+1 untuk opsi mendalam, terlepas dari betapa sulitnya penerapannya.

+2 untuk ini - tidak sulit untuk diterapkan, ini adalah masalah prinsip (yaitu tidak menerapkan solusi yang kurang sempurna). Namun, seperti yang disebutkan di atas, ini digunakan di perpustakaan jQuery dan cukup berguna di semua kecuali beberapa kasus tepi. Akan menyenangkan untuk memilikinya tersedia di perpustakaan ringan seperti ini.

@kmalakoff telah menulis implementasi , Anda harus memberinya umpan balik! :)

Anda bahkan dapat menggabungkan _.cloneToDepth asli saya dengan _clone dan hanya menambahkan parameter kedalaman....

  // 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;
  };

Juga, saya menulis _.own dan _.disown yang memperkenalkan sebuah konvensi untuk kepemilikan (baik pasangan dari retain/release atau clone/destroy). Itu hanya berulang satu tingkat ke bawah, tapi saya kira mungkin ada opsi yang ditambahkan untuk rekursi total (saya ingin melihat kasus penggunaan!).

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

Dan jika Anda menginginkan tujuan umum, klon yang dapat diperluas:

  // 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;
  };

Saya tidak yakin bahwa ini adalah ide yang bagus sampai kasus penggunaan diberikan di mana salinan yang dalam sebenarnya adalah solusi terbaik. Saya pikir akan sulit untuk menemukannya.

Saya tidak sepenuhnya puas dengan tanggapan saya kemarin (seharusnya tidak menulis kode setelah tengah malam)...Saya telah membuat dua versi ( _.cloneToDepth pada dasarnya hanya mengkloning wadah, mempertahankan referensi ke objek asli dan _.deepClone yang menyalin instance):

  // 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;
  };

Seperti yang ditunjukkan oleh @michaelficarra , kasus penggunaan mungkin tidak jelas. Secara pribadi, saya menggunakan:

1) _.own / _.disown ketika saya ingin berbagi objek yang memiliki siklus hidup yang kompleks dan/atau model kepemilikan (seperti penghitungan referensi untuk menangani pembersihan dengan benar)
2) Saya menggunakan _.cloneToDepth / _.containerClone (jarang!) ketika saya memiliki opsi bersarang yang kompleks untuk suatu fungsi.
3) Saya tidak pernah membutuhkan _.deepClone , tetapi anggap itu bisa berguna sebagai metode tujuan umum _.clone jika Anda menulis fungsi generik yang secara fleksibel mendukung tipe (mis. peduli apa yang Anda berikan kepada saya, tetapi saya akan memodifikasinya dan tidak ingin memiliki efek samping pada aslinya - meskipun string adalah kasing khusus yang tidak dapat diubah).

Saya telah mengirimkan kode dan tes di sini: https://github.com/kmalakoff/underscore-awesomer/commit/0cf6008f16ad6e6daf60caf456021693ef33fec5

Catatan: mereka yang terjebak mencari klon dalam satu baris yang tidak lengkap untuk objek sederhana dengan semantik yang dapat diprediksi hanya dapat JSON.parse(JSON.stringify(object)) .

@adamhooper Apakah metode ini cenderung kehilangan properti atau apa? Mengapa tidak lengkap?

@diversario Itu tidak akan menyalin prototipe objek, dan itu tidak akan menyalin fungsi. Itu berlaku secara rekursif - jadi itu tidak akan menyalin prototipe atau fungsi objek bersarang dengan benar.

Khususnya: itu tidak akan menyalin Tanggal apa pun dengan benar di pohon objek Anda. Dan jika Anda ingin memperbaikinya agar berfungsi dengan Dates, Anda hanya menangani gejala kecil dari masalah yang jauh lebih besar.

Oh, benar. Saya kebanyakan menggunakannya untuk memutuskan referensi ke hal-hal seperti objek "templat", jadi saya belum pernah mengalami hal seperti itu. Tetapi saya melihat perlunya salinan yang sangat dalam.

Saya telah mengubah mixin deepExtend Kurt Milam menjadi paket npm.

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

@michaelficarra , tolong jelaskan bagaimana penyalinan dalam bukan solusi yang baik untuk mengkloning struktur pohon generik?

Untuk Anda semua yang mencoba menggunakan salinan mendalam @adamhooper one-liner, ketahuilah bahwa itu tidak berfungsi untuk tanggal
JSON.parse(JSON.stringify(objek))
sebenarnya itu mengubah objek Date menjadi string

-1 untuk salinan yang dalam. Menggunakan rantai prototipe untuk objek konfigurasi akan selalu lebih sesuai dengan api Anda daripada opsi bersarang.

Sebenarnya ada tempat untuk deep copy, tetapi harus bertepatan dengan pengecekan tipe, jadi menurut saya ini adalah use case yang sangat spesifik, dan bukan untuk tujuan umum. Lebih cocok untuk pustaka skema JSON, atau pemuat konfigurasi. Bukan sabuk alat javascript.

Dalam kebanyakan kasus _.extend({}, obj1, { prop1: 1, prop2: 2 }) adalah apa yang benar-benar perlu saya lakukan yang:

  • memberi saya objek baru
  • tidak mengubah objek sumber saya dan
  • bukan salinan dalam
_.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)});
  };

Kami sering mengalami kasus penggunaan di lingkungan kerja kami yang akan mendapat manfaat dari kloning mendalam.

Terkadang, Anda harus melakukan migrasi database, ketika fitur aplikasi web yang rumit diperbarui dan diperluas ke paradigma baru. Salinan mendalam dari data kami sangat membantu ketika harus melakukan operasi seperti menghasilkan hash dari data ketika referensi asli lebih disukai untuk tidak dimanipulasi. Saya tidak berpikir satu dokumen dalam database besar kami adalah 'datar'. Properti bersarang ada di mana-mana.

Juga, terkadang Anda ingin mengirim salinan objek ke layanan berbeda dalam aplikasi web Anda, untuk dioperasikan sebelum memutuskan apa yang harus dilakukan dengannya. Memiliki properti bersarang dengan referensi mengalahkan tujuan memiliki objek kloning. Intinya SELURUH, imo, dari objek kloning adalah meninggalkan referensi asli dengan bijaksana. Segera setelah Anda memperkenalkan referensi untuk tingkat yang lebih dalam, seluruh tujuannya dapat diperdebatkan.

Saya yakin ada cara yang layak untuk melakukan ini di mana suatu objek dapat dinavigasi ke kedalamannya, kemudian direkonstruksi dari lapisan terdalam hingga ke akarnya.

Apakah halaman ini membantu?
0 / 5 - 0 peringkat

Masalah terkait

githublyp picture githublyp  ·  3Komentar

acl0056 picture acl0056  ·  5Komentar

danilopolani picture danilopolani  ·  5Komentar

arypbatista picture arypbatista  ·  3Komentar

chikamichi picture chikamichi  ·  8Komentar