JavaScript setara dengan metode perluasan jQuery

94

Latar Belakang

Saya memiliki fungsi yang mengambil configobjek sebagai argumen. Di dalam fungsinya, saya juga punya defaultobjek. Masing-masing objek tersebut berisi properti yang pada dasarnya berfungsi sebagai pengaturan untuk sisa kode di dalam fungsi tersebut. Untuk mencegah keharusan menentukan semua pengaturan dalam configobjek, saya menggunakan extendmetode jQuery untuk mengisi objek baru, settingsdengan nilai default apa pun dari defaultobjek jika tidak ditentukan dalam configobjek:

var config = {key1: value1};
var default = {key1: default1, key2: default2, key 3: default 3};

var settings = $.extend(default, config);

//resulting properties of settings:
settings = {key1: value1, key2: default2, key 3: default 3};

Masalah

Ini berfungsi dengan baik, tetapi saya ingin mereproduksi fungsionalitas ini tanpa perlu jQuery. Apakah ada cara yang sama elegan (atau mendekati) untuk melakukan ini dengan javascript biasa?


Edit: Justifikasi Non-Duplikat

Pertanyaan ini bukan duplikat dari pertanyaan " Bagaimana cara menggabungkan properti dua objek JavaScript secara dinamis? ". Sedangkan pertanyaan itu hanya ingin membuat sebuah objek yang berisi semua kunci dan nilai dari dua objek yang terpisah - Saya secara khusus ingin membahas bagaimana melakukan ini jika kedua objek berbagi beberapa tetapi tidak semua kunci dan objek mana yang akan diutamakan ( default) untuk objek yang dihasilkan jika ada kunci duplikat. Dan bahkan lebih khusus lagi, saya ingin membahas penggunaan metode jQuery untuk mencapai ini dan menemukan cara alternatif untuk melakukannya tanpa jQuery. Meskipun banyak dari jawaban untuk kedua pertanyaan tersebut tumpang tindih, itu tidak berarti bahwa pertanyaan itu sendiri sama.

mbeasley.dll
sumber
8
Curi saja cara jQuery melakukannya james.padolsey.com/jquery/#v=1.6.2&fn=jQuery.extend :-P
Rocket Hazmat
Ide bagus @RocketHazmat, perhatikan jika Anda copypasta yang berfungsi itu memiliki beberapa dependensi jQuery lain seperti jQuery.isPlainObjectdanjQuery.isFunction
Andy Raddatz

Jawaban:

133

Untuk mendapatkan hasil dalam kode Anda, Anda akan melakukan:

function extend(a, b){
    for(var key in b)
        if(b.hasOwnProperty(key))
            a[key] = b[key];
    return a;
}

Perlu diingat bahwa cara Anda menggunakan perluasan di sana akan mengubah objek default. Jika Anda tidak menginginkannya, gunakan

$.extend({}, default, config)

Solusi yang lebih kuat yang meniru fungsionalitas jQuery adalah sebagai berikut:

function extend(){
    for(var i=1; i<arguments.length; i++)
        for(var key in arguments[i])
            if(arguments[i].hasOwnProperty(key))
                arguments[0][key] = arguments[i][key];
    return arguments[0];
}
Ryan Lynch
sumber
1
Saya ingin melihat contoh kode beraksi, mungkin biola, karena plain js jauh lebih keren tapi sangat abstrak, terima kasih banyak.
thednp
3
ini tidak berulang, seperti $ .extend
ekkis
30

Anda dapat menggunakan Object.assign.

var defaults = {key1: "default1", key2: "default2", key3: "defaults3"};
var config = {key1: "value1"};

var settings = Object.assign({}, defaults, config); // values in config override values in defaults
console.log(settings); // Object {key1: "value1", key2: "default2", key3: "defaults3"}

Ini menyalin nilai dari semua properti enumerable sendiri dari satu atau lebih objek sumber ke objek target dan mengembalikan objek target.

Object.assign(target, ...sources)

Ia bekerja di semua browser desktop kecuali IE (tetapi termasuk Edge). Ini telah mengurangi dukungan seluler.

Lihat sendiri di sini: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/assign

Tentang deep copy

Namun, Object.assign tidak memiliki opsi dalam yang dimiliki metode perluasan jQuery.

Catatan: Anda biasanya dapat menggunakan JSON untuk efek yang serupa

var config = {key1: "value1"};
    var defaults = {key1: "default1", key2: "default2", keyDeep: {
        kd1: "default3",
        kd2: "default4"
    }};
    var settings = JSON.parse(JSON.stringify(Object.assign({}, defaults, config))); 
    console.log(settings.keyDeep); // Object {kd1: "default3", kd2: "default4"}
ling
sumber
Ya, tapi di sini ia melompati key1 dari default dan tidak menambahkannya ke objek baru.
Ionut Necula
@Anonymous Sebenarnya itu menambahkannya, tapi itu diganti dengan nilai key1 dari config array.
ling
Saya kira itu yang terjadi. Jadi, itu tidak menambahkannya setelah semua.
Ionut Necula
29

Anda dapat menggunakan operator penyebaran ECMA 2018 dalam literal objek ...

var config = {key1: value1};
var default = {key1: default1, key2: default2, key 3: default 3};

var settings = {...default, ...config}

//resulting properties of settings:
settings = {key1: value1, key2: default2, key 3: default 3};

Dukungan BabelJS untuk browser lama

Yaron
sumber
2
Banyak cara yang paling rapi dan terbersih untuk melakukannya!
Velojet
4

Ini adalah pendekatan saya yang sedikit berbeda dengan deep copy yang saya buat saat mencoba menghilangkan ketergantungan jQuery. Ini sebagian besar dirancang untuk menjadi kecil sehingga mungkin tidak semua fitur yang diharapkan. Harus sepenuhnya kompatibel dengan ES5 (mulai dari IE9 karena penggunaan Object.keys ):

function extend(obj1, obj2) {
    var keys = Object.keys(obj2);
    for (var i = 0; i < keys.length; i += 1) {
      var val = obj2[keys[i]];
      obj1[keys[i]] = ['string', 'number', 'array', 'boolean'].indexOf(typeof val) === -1 ? extend(obj1[keys[i]] || {}, val) : val;
    }
    return obj1;
  }

Anda mungkin bertanya-tanya apa yang sebenarnya dilakukan oleh baris kelima ... Jika obj2.key adalah sebuah objek literal (yaitu jika itu bukan tipe biasa) kita secara rekursif memanggil perluasan padanya. Jika properti dengan nama itu belum ada di obj1, kami menginisialisasi ke objek kosong terlebih dahulu. Jika tidak, kita cukup mengatur obj1.key ke obj2.key.

Berikut adalah beberapa tes moka / chai saya yang seharusnya membuktikan kasus umum berfungsi di sini:

it('should extend a given flat object with another flat object', () => {
  const obj1 = {
    prop1: 'val1',
    prop2: 42,
    prop3: true,
    prop4: 20.16,
  };
  const obj2 = {
    prop4: 77.123,
    propNew1: 'newVal1',
    propNew2: 71,
  };
  assert.deepEqual(utils.extend(obj1, obj2), {
    prop1: 'val1',
    prop2: 42,
    prop3: true,
    prop4: 77.123,
    propNew1: 'newVal1',
    propNew2: 71,
  });
});

it('should deep-extend a given flat object with a nested object', () => {
  const obj1 = {
    prop1: 'val1',
    prop2: 'val2',
  };
  const obj2 = {
    propNew1: 'newVal1',
    propNew2: {
      propNewDeep1: 'newDeepVal1',
      propNewDeep2: 42,
      propNewDeep3: true,
      propNewDeep4: 20.16,
    },
  };
  assert.deepEqual(utils.extend(obj1, obj2), {
    prop1: 'val1',
    prop2: 'val2',
    propNew1: 'newVal1',
    propNew2: {
      propNewDeep1: 'newDeepVal1',
      propNewDeep2: 42,
      propNewDeep3: true,
      propNewDeep4: 20.16,
    },
  });
});

it('should deep-extend a given nested object with another nested object and deep-overwrite members', () => {
  const obj1 = {
    prop1: 'val1',
    prop2: {
      propDeep1: 'deepVal1',
      propDeep2: 42,
      propDeep3: true,
      propDeep4: {
        propDeeper1: 'deeperVal1',
        propDeeper2: 777,
        propDeeper3: 'I will survive',
      },
    },
    prop3: 'lone survivor',
  };
  const obj2 = {
    prop1: 'newVal1',
    prop2: {
      propDeep1: 'newDeepVal1',
      propDeep2: 84,
      propDeep3: false,
      propDeep4: {
        propDeeper1: 'newDeeperVal1',
        propDeeper2: 888,
      },
    },
  };
  assert.deepEqual(utils.extend(obj1, obj2), {
    prop1: 'newVal1',
    prop2: {
      propDeep1: 'newDeepVal1',
      propDeep2: 84,
      propDeep3: false,
      propDeep4: {
        propDeeper1: 'newDeeperVal1',
        propDeeper2: 888,
        propDeeper3: 'I will survive',
      },
    },
    prop3: 'lone survivor',
  });
});

Saya akan senang dengan umpan balik atau komentar tentang penerapan ini. Terima kasih sebelumnya!

Rico Pfaus
sumber
2

Anda dapat melakukan loop melalui properti Object menggunakan forpernyataan.

var settings = extend(default, config);

function extend(a, b){
    var c = {};
    for(var p in a)
        c[p] = (b[p] == null) ? a[p] : b[p];
    return c;
}
Ivan Kuckir
sumber
2
Itu tidak akan memperhitungkan properti yang tidak ada di a. Meskipun tidak ada dalam contoh, $.extendsebenarnya berfungsi seperti itu, menggabungkan semua properti dari semua objek.
Felix Kling
1
Saat saya membaca kode dasar jquery, sebenarnya ia melakukan fungsi rekursif untuk menyalin atau mengkloning nilainya. Jadi sangat dalam melakukan clone / copy, bukan sekedar copy di level pertama seperti kode diatas.
aladine
2

Saya lebih suka kode ini yang menggunakan forEachIn generik saya, dan tidak merusak objek pertama:

function forEachIn(obj, fn) {
    var index = 0;
    for (var key in obj) {
        if (obj.hasOwnProperty(key)) {
            fn(obj[key], key, index++);
        }
    }
}

function extend() {
    var result = {};
    for (var i = 0; i < arguments.length; i++) {
        forEachIn(arguments[i],
            function(obj, key) {
                result[key] = obj;
            });
    }
    return result;
}

Jika Anda benar-benar ingin menggabungkan barang ke objek pertama, Anda dapat melakukan:

obj1 = extend(obj1, obj2);
GeeWhizBang
sumber
Sejauh ini, inilah jawaban terbaik di sini. Ia melakukan deep copy, tidak merusak objek pertama, mendukung objek tak terbatas, dan dapat mengubah tipe sambil memperluas (yaitu, banyak jawaban di sini tidak akan memperluas string ke objek, jawaban ini akan). Ini mendapat setiap tanda centang di buku saya. penggantian perpanjangan penuh, dan sangat mudah dipahami.
Nick Steele
0

Ini sangat membantu saya ketika saya mengembangkan dengan javascript murni.

function extends(defaults, selfConfig){
     selfConfig = JSON.parse(JSON.stringify(defaults));
     for (var item in config) {
         if (config.hasOwnProperty(item)) {
             selfConfig[item] = config[item];
         }
     }
     return selfConfig;
}
Alan Ktquez
sumber
0

Pendekatan Ivan Kuckir juga dapat diadaptasi untuk membuat prototipe objek baru:

Object.prototype.extend = function(b){
for(var key in b)
    if(b.hasOwnProperty(key))
        this[key] = b[key];
    return this;
}

var settings = default.extend(config);
Doug Manuel
sumber