Équivalent JavaScript de la méthode extend de jQuery
arrière-plan
j'ai une fonction qui prend un objet config
comme argument. Dans la fonction, j'ai aussi l'objet default
. Chacun de ces objets contient des propriétés qui fonctionnent essentiellement comme des paramètres pour le reste du code dans la fonction. Afin d'éviter d'avoir à spécifier tous les paramètres dans le config
objet, j'utilise jQuery extend
méthode pour remplir un nouvel objet, settings
avec toutes les valeurs par défaut de l'objet default
s'il n'était pas spécifié dans l'objet config
:
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};
problème
cela fonctionne très bien, mais j'aimerais reproduire cette fonctionnalité sans avoir besoin de jQuery. Y a-t-il un moyen tout aussi élégant (ou proche) de le faire avec le bon vieux javascript?
Edit: Non-Duplicate Justification
cette question n'est pas une copie de " comment fusionner dynamiquement les propriétés de deux objets JavaScript? la " question. Alors que cette question veut simplement créer un objet qui contient toutes les clés et les valeurs de deux objets séparés - je veux spécifiquement aborder la façon de le faire dans le cas où les deux objets partagent une partie mais pas toutes les clés et quel objet obtiendra priorité (par défaut) pour l'objet résultant dans le cas où il ya des clés dupliquées. Et même plus précisément, je voulais aborder l'utilisation de la méthode de jQuery pour atteindre cet objectif et trouver une autre façon de le faire sans jQuery. Bien que bon nombre des réponses aux deux questions se recoupent, cela ne signifie pas que les questions elles-mêmes sont les mêmes.
8 réponses
Pour obtenir le résultat dans votre code, vous feriez:
function extend(a, b){
for(var key in b)
if(b.hasOwnProperty(key))
a[key] = b[key];
return a;
}
gardez à l'esprit que la façon dont vous avez utilisé extend va modifier l'objet par défaut. Si vous ne le voulez pas, utilisez
$.extend({}, default, config)
une solution plus robuste qui imite la fonctionnalité de jQuery serait la suivante:
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];
}
vous pouvez utiliser L'objet.assigner.
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"}
il copie les valeurs de toutes les propriétés propres énumérables d'un ou plusieurs objets source à un objet cible et renvoie l'objet cible.
Object.assign(target, ...sources)
il fonctionne dans tous les navigateurs de bureau sauf IE (mais y compris Edge). Il a atténué le soutien mobile.
Voyez par vous-même ici: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/assign
à propos de deep copy
Cependant, Object.assign n'a pas l'option deep que possède la méthode extend de jQuery.
Note: Vous pouvez généralement utiliser JSON pour un effet similaire bien que
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"}
Vous pouvez utiliser l'ECMA 2015 la propagation de l'opérateur ...
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};
vous pouvez faire une boucle à travers les propriétés de L'objet en utilisant la déclaration for
.
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;
}
C'est mon approche légèrement différente avec deep copy que j'ai inventé tout en essayant d'éliminer une dépendance jQuery. Il est principalement conçu pour être petit de sorte qu'il pourrait avoir pas toutes les fonctionnalités que l'on attend. Devrait être entièrement compatible ES5 (à partir de IE9 en raison de l'utilisation de objet.touches ):
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;
}
vous pouvez vous demander ce que fait exactement la cinquième ligne ... Si obj2.la clé est un objet littéral (c.-à-d. si ce n'est pas un type ordinaire) nous récursivement appel d'étendre sur elle. Si une propriété avec ce nom n'existe pas encore dans obj1, nous l'initialisons d'abord à un objet vide. Sinon, nous plaçons obj1.la clé de l'obj2.clé.
voici quelques-uns de mes tests mocha/chai qui devraient prouver les cas communs à travailler ici:
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',
});
});
je serais heureux des commentaires ou des commentaires sur cette mise en œuvre. Merci à l'avance!
je préfère ce code qui utilise mon forEachIn générique, et ne commande pas le premier objet:
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;
}
si vous voulez vraiment fusionner des choses dans le premier objet, vous pouvez le faire:
obj1 = extend(obj1, obj2);
L'approche du D'Ivan Kuckir peut également être adaptée pour créer un nouveau prototype d'objet:
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);
cela m'aide beaucoup quand je développe avec javascript pur.
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;
}