110 votes

équivalent JavaScript de la méthode extend de jQuery

Contexte

J'ai une fonction qui prend un config en tant qu'argument. Dans la fonction, j'ai aussi default objet. Chacun de ces objets contient des propriétés qui servent essentiellement de paramètres pour le reste du code de la fonction. Afin d'éviter d'avoir à spécifier tous les paramètres dans l'objet config j'utilise l'objet jQuery extend pour remplir un nouvel objet, settings avec toutes les valeurs par défaut de la default s'ils n'ont pas été spécifiés dans l'objet config objet :

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 recours à jQuery. Existe-t-il un moyen aussi élégant (ou presque) de le faire avec le bon vieux javascript ?


Editer : Justification non dupliquée

Cette question n'est pas un doublon de la question " Comment fusionner dynamiquement les propriétés de deux objets JavaScript ? Question ". Alors que cette question vise simplement à créer un objet qui contient toutes les clés et les valeurs de deux objets distincts, je veux spécifiquement aborder la façon de procéder dans le cas où les deux objets partagent certaines clés mais pas toutes, et quel objet aura la priorité (par défaut) pour l'objet résultant dans le cas où il y a des clés en double. Et plus spécifiquement encore, je voulais aborder l'utilisation de la méthode de jQuery pour y parvenir et trouver un autre moyen de le faire sans jQuery. Si de nombreuses réponses aux deux questions se recoupent, cela ne signifie pas que les questions elles-mêmes sont les mêmes.

9 votes

Il suffit de voler la façon dont jQuery le fait james.padolsey.com/jquery/#v=1.6.2&fn=jQuery.extend :-P

0 votes

Bonne idée @RocketHazmat, notez que si vous copiez cette fonction, elle a quelques autres dépendances jQuery comme jQuery.isPlainObject y jQuery.isFunction

1 votes

@RocketHazmat quelqu'un l'a déjà fait : gomakethings.com/vanilla-javascript-version-of-jquery-extend/

139voto

Ryan Lynch Points 5531

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'extension modifiera l'objet par défaut. Si vous ne voulez pas cela, 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];
}

1 votes

J'aimerais voir un exemple de code en action, peut-être fiddle, parce que le js simple est tellement plus cool mais tellement abstrait, merci beaucoup.

5 votes

Ce n'est pas récurrent, comme le fait $.extend

60voto

Yaron Points 1529

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

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 anciens navigateurs

33voto

ling Points 177

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

Elle copie les valeurs de toutes les propriétés propres énumérables d'un ou plusieurs objets sources 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 dispose d'un support mobile atténué.

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

A propos de la copie profonde

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

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

10voto

Rico Pfaus Points 191

Voici mon approche légèrement différente de la copie profonde que j'ai trouvée en essayant d'éliminer une dépendance de jQuery. Il est principalement conçu pour être petit, donc il peut ne pas avoir toutes les fonctionnalités attendues. Devrait être entièrement compatible avec ES5 (à partir d'IE9 en raison de l'utilisation de Clé d'objet ) :

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 si ce n'est pas 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 mettons simplement obj1.key en 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 réactions ou des commentaires sur cette mise en œuvre. Merci d'avance !

3voto

GeeWhizBang Points 37

Je préfère ce code qui utilise mon forEachIn générique, et qui n'écrase 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 le faire :

obj1 = extend(obj1, obj2);

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X