É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.

73
demandé sur Cœur 2012-06-26 00:56:24

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];
}
119
répondu Ryan Lynch 2012-06-25 21:09:08

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"}
15
répondu ling 2016-08-28 05:20:29

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

BabelJS soutien pour les anciens navigateurs

5
répondu Yaron 2018-02-06 00:24:30

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;
}
2
répondu Ivan Kuckir 2016-07-15 20:23:12

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!

2
répondu Rico Pfaus 2016-11-20 15:23:21

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);
2
répondu GeeWhizBang 2016-11-21 16:42:24

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);
0
répondu Doug Manuel 2017-05-23 10:31:23

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;
}
-1
répondu Alan Ktquez 2016-03-24 14:48:07