web-dev-qa-db-fra.com

Équivalent JavaScript de la méthode extend de jQuery

Contexte

J'ai une fonction qui prend un objet config en argument. Dans la fonction, j'ai également 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 de spécifier tous les paramètres dans l’objet config, j’utilise la méthode extend de jQuery pour renseigner un nouvel objet, settings, avec les valeurs par défaut de default object s'ils n'étaient pas spécifiés 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. Existe-t-il un moyen tout aussi élégant (ou proche de) de le faire avec javascript?


Edit: Justification non dupliquée

Cette question n'est pas une copie de la question " Comment puis-je fusionner des propriétés de deux objets JavaScript de manière dynamique? ". Alors que cette question veut simplement créer un objet qui contient toutes les clés et valeurs de deux objets distincts - 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é ( 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 identiques.

82
mbeasley

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é l'étendre modifiera l'objet par défaut. Si vous ne le souhaitez pas, utilisez

$.extend({}, default, config)

Une solution plus robuste imitant les fonctionnalités 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];
}
129
Ryan Lynch

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)

Cela fonctionne dans tous les navigateurs de bureau sauf IE (mais avec Edge). Il prend en charge le support mobile.

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

À propos de la copie en profondeur

Cependant, Object.assign n'a pas l'option deep que possède la méthode extend de jQuery.

Remarque: 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"}
20
ling

Vous pouvez utiliser l'ECMA 2018 opérateur d'étalement dans les objets littéraux ...

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

support de BabelJS pour les navigateurs plus anciens

19
Yaron

C’est mon approche légèrement différente avec la copie profonde que j’ai imaginée en essayant d’éliminer une dépendance à jQuery. Il est principalement conçu pour être petit, de sorte qu'il ne possède peut-être pas toutes les fonctionnalités attendues. Devrait être entièrement compatible ES5 (à partir de IE9 en raison de l'utilisation de 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 vous demandez peut-être ce que fait exactement la cinquième ligne ... Si obj2.key est un littéral d'objet (c'est-à-dire s'il ne s'agit pas d'un type ordinaire), nous appelons récursivement l'étendre. Si une propriété portant ce nom n'existe pas encore dans obj1, nous l'initialisons d'abord avec un objet vide. Sinon, nous définissons simplement obj1.key sur obj2.key.

Voici quelques-uns de mes tests moka/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 implémentation. Merci d'avance!

2
Rico Pfaus

Je préfère ce code qui utilise mon générique forEachIn 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);
2
GeeWhizBang

Vous pouvez parcourir les propriétés de l'objet à l'aide de l'instruction 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;
}
2
Ivan Kuckir

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;
}
0
Alan Ktquez

L'approche d'Ivan Kuckir peut également être adapté 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);
0
Doug Manuel