tło
Mam funkcję, która przyjmuje config
obiekt jako argument. W ramach funkcji mam również default
obiekt. Każdy z tych obiektów zawiera właściwości, które zasadniczo działają jako ustawienia dla pozostałej części kodu w funkcji. Aby uniknąć konieczności określania wszystkich ustawień w config
obiekcie, używam extend
metody jQuery do wypełnienia nowego obiektu, settings
z dowolnymi wartościami domyślnymi z default
obiektu, jeśli nie zostały określone w config
obiekcie:
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};
Problem
To działa świetnie, ale chciałbym odtworzyć tę funkcjonalność bez potrzeby korzystania z jQuery. Czy istnieje równie elegancki (lub zbliżony) sposób zrobienia tego za pomocą zwykłego javascript?
Edycja: Wyrównanie bez powtórzeń
To pytanie nie jest powtórzeniem pytania „ Jak mogę dynamicznie scalać właściwości dwóch obiektów JavaScript? ”. Podczas gdy to pytanie chce po prostu utworzyć obiekt, który zawiera wszystkie klucze i wartości z dwóch oddzielnych obiektów - szczególnie chcę się zająć, jak to zrobić w przypadku, gdy oba obiekty mają niektóre, ale nie wszystkie klucze, i który obiekt uzyska pierwszeństwo wartość domyślna) dla wynikowego obiektu w przypadku zduplikowanych kluczy. A dokładniej, chciałem zająć się wykorzystaniem metody jQuery, aby to osiągnąć i znaleźć alternatywny sposób na zrobienie tego bez jQuery. Chociaż wiele odpowiedzi na oba pytania pokrywa się, nie oznacza to, że same pytania są takie same.
źródło
jQuery.isPlainObject
ijQuery.isFunction
Odpowiedzi:
Aby uzyskać wynik w swoim kodzie, wykonaj następujące czynności:
function extend(a, b){ for(var key in b) if(b.hasOwnProperty(key)) a[key] = b[key]; return a; }
Pamiętaj, że sposób, w jaki użyłeś rozszerzenia, zmodyfikuje domyślny obiekt. Jeśli tego nie chcesz, użyj
$.extend({}, default, config)
Bardziej niezawodne rozwiązanie, które naśladuje funkcjonalność jQuery, wyglądałoby następująco:
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]; }
źródło
Możesz użyć 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"}
Kopiuje wartości wszystkich wyliczalnych właściwości własnych z co najmniej jednego obiektu źródłowego do obiektu docelowego i zwraca obiekt docelowy.
Object.assign(target, ...sources)
Działa we wszystkich przeglądarkach komputerowych z wyjątkiem IE (ale w tym Edge). Ma złagodzone wsparcie mobilne.
Przekonaj się tutaj: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/ assign
O głębokiej kopii
Jednak Object. assign nie ma opcji deep, którą ma metoda rozszerzenia jQuery.
Uwaga: generalnie możesz użyć JSON dla podobnego efektu
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"}
źródło
W literałach obiektów można używać operatora spreadu ECMA 2018 ...
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};
Obsługa BabelJS dla starszych przeglądarek
źródło
To jest moje nieco inne podejście do głębokiego kopiowania, które wymyśliłem, próbując wyeliminować zależność od jQuery. Jest głównie zaprojektowany jako mały, więc może nie mieć wszystkich funkcji, których można się spodziewać. Powinien być w pełni kompatybilny z ES5 (począwszy od IE9 ze względu na użycie 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; }
Możesz się zastanawiać, co dokładnie robi piąta linia ... Jeśli obj2.key jest literałem obiektowym (tj. Jeśli nie jest to zwykły typ), rekurencyjnie wywołujemy na nim rozszerzenie. Jeśli właściwość o tej nazwie jeszcze nie istnieje w obj1, najpierw inicjalizujemy ją w pustym obiekcie. W przeciwnym razie po prostu ustawiamy obj1.key na obj2.key.
Oto niektóre z moich testów mokki / herbaty, które powinny udowodnić, że sprawdzają się tutaj typowe przypadki:
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', }); });
Byłbym zadowolony z opinii lub komentarzy na temat tej implementacji. Z góry dziękuję!
źródło
Możesz przeglądać właściwości Object za pomocą
for
instrukcji.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; }
źródło
a
. Chociaż nie ma tego w przykładzie, w$.extend
rzeczywistości działa w ten sposób, łącząc wszystkie właściwości wszystkich obiektów.Wolę ten kod, który używa mojego ogólnego forEachIn i nie zmienia pierwszego obiektu:
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; }
Jeśli naprawdę chcesz scalić rzeczy w pierwszy obiekt, możesz zrobić:
źródło
Bardzo mi pomaga, kiedy pracuję w czystym javascript.
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; }
źródło
Podejście Ivana Kuckira można również dostosować do stworzenia nowego prototypu obiektu:
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);
źródło