Équivalent JavaScript de la méthode extend de jQuery

92

Contexte

J'ai une fonction qui prend un configobjet comme argument. Dans la fonction, j'ai aussi un defaultobjet. 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 de l' configobjet, j'utilise la extendméthode de jQuery pour remplir un nouvel objet, settingsavec toutes les valeurs par défaut de l' defaultobjet si elles n'ont pas été spécifiées dans l' configobjet:

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. Existe-t-il un moyen tout aussi élégant (ou proche de) de le faire avec du javascript ordinaire?


Edit: Justification non dupliquée

Cette question n'est pas un double de la question " Comment fusionner dynamiquement les propriétés de deux objets JavaScript? ". Alors que cette question veut simplement créer un objet qui contient toutes les clés et valeurs de deux objets séparés - je veux spécifiquement expliquer comment faire cela dans le cas où les deux objets partagent certaines mais pas toutes les clés et quel objet aura la priorité ( la valeur par défaut) pour l'objet résultant dans le cas où il y aurait des clés en double. Et plus précisément encore, je voulais aborder l'utilisation de la méthode de jQuery pour y parvenir et trouver un moyen alternatif de le faire sans jQuery. Bien que de nombreuses réponses aux deux questions se chevauchent, cela ne signifie pas que les questions elles-mêmes sont les mêmes.

Mbeasley
la source
8
Volez simplement la façon dont jQuery le fait james.padolsey.com/jquery/#v=1.6.2&fn=jQuery.extend :-P
Rocket Hazmat
Bonne idée @RocketHazmat, notez que si vous copypasta cette fonction, elle a quelques autres dépendances jQuery comme jQuery.isPlainObjectetjQuery.isFunction
Andy Raddatz

Réponses:

131

Pour obtenir le résultat dans votre code, vous devez faire:

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é l'extension modifiera 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];
}
Ryan Lynch
la source
1
J'aimerais voir un exemple de code en action, peut-être du violon, parce que le js simple est tellement plus cool mais tellement abstrait, merci un million.
thednp
3
cela ne se répète pas, comme le fait $ .extend
ekkis
30

Vous pouvez utiliser 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"}

Il copie les valeurs de toutes les propriétés propres énumérables d'un ou plusieurs objets source vers 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 support mobile.

Voyez par vous-même ici: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/assign

À propos de la copie profonde

Cependant, Object.assign n'a pas l'option profonde de la méthode extend de jQuery.

Remarque: vous pouvez généralement utiliser JSON pour un effet similaire

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"}
lingue
la source
Ouais, mais ici, il saute par-dessus la clé1 par défaut et ne l'ajoute pas au nouvel objet.
Ionut Necula
@Anonymous En fait, il l'ajoute, mais il est remplacé par la valeur key1 du tableau de configuration.
ling
J'ai supposé que cela se passait. Donc, ce n'est pas l'ajout après tout.
Ionut Necula
4

C'est mon approche légèrement différente avec la copie profonde que j'ai trouvée en essayant d'éliminer une dépendance jQuery. Il est principalement conçu pour être petit, il se peut donc qu'il n'ait pas toutes les fonctionnalités attendues. Doit être entièrement compatible ES5 (à partir de IE9 en raison de l'utilisation d' 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;
  }

Vous pouvez vous demander ce que fait exactement la cinquième ligne ... Si obj2.key est un objet littéral (c'est-à-dire s'il ne s'agit pas d'un type ordinaire), nous appelons récursivement extend sur lui. Si une propriété avec ce nom n'existe pas encore dans obj1, nous l'initialisons d'abord à un objet vide. Sinon, nous définissons simplement obj1.key sur obj2.key.

Voici quelques-uns de mes tests mocha / chai qui devraient prouver que les cas courants fonctionnent 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 de recevoir des commentaires ou des commentaires sur cette mise en œuvre. Merci d'avance!

Rico Pfaus
la source
2

Vous pouvez parcourir les propriétés de l'objet à l'aide de forstatement.

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
la source
2
Cela ne prendra pas en compte les propriétés qui n'existent pas dans a. Bien que ce ne soit pas dans l'exemple, $.extendfonctionne en fait de cette façon, en fusionnant toutes les propriétés de tous les objets.
Felix Kling
1
Lorsque je lis le code de base de jquery, il exécute en fait une fonction récursive pour copier ou cloner la valeur. Il s'agit donc de cloner / copier profondément, pas seulement de copier au premier niveau comme le code ci-dessus.
aladine
2

Je préfère ce code qui utilise mon forEachIn générique, et ne modifie 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 éléments dans le premier objet, vous pouvez faire:

obj1 = extend(obj1, obj2);
GeeWhizBang
la source
C'est de loin la meilleure réponse ici. Il fait une copie profonde, ne modifie pas le premier objet, prend en charge des objets infinis et peut modifier les types tout en s'étendant (c'est-à-dire que de nombreuses réponses ici ne vont pas étendre une chaîne à un objet, cette réponse le fera). Cela obtient chaque coche dans mon livre. remplacement complet et extrêmement facile à comprendre.
Nick Steele
0

Cela m'aide beaucoup lorsque je développe avec du 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;
}
Alan Ktquez
la source
0

L'approche 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);
Doug Manuel
la source