JavaScript-Äquivalent zur Erweiterungsmethode von jQuery

92

Hintergrund

Ich habe eine Funktion, die ein configObjekt als Argument nimmt. Innerhalb der Funktion habe ich auch defaultObjekt. Jedes dieser Objekte enthält Eigenschaften, die im Wesentlichen als Einstellungen für den Rest des Codes innerhalb der Funktion dienen. Um zu vermeiden, dass alle Einstellungen innerhalb des configObjekts angegeben werden müssen, verwende ich die extendMethode von jQuery , um ein neues Objekt settingsmit Standardwerten aus dem defaultObjekt auszufüllen, wenn diese nicht im configObjekt angegeben wurden:

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

Problem

Dies funktioniert hervorragend, aber ich möchte diese Funktionalität ohne die Notwendigkeit von jQuery reproduzieren. Gibt es ein ebenso elegantes (oder nahes) Mittel, um dies mit einfachem altem Javascript zu tun?


Bearbeiten: Nicht doppelte Begründung

Diese Frage ist kein Duplikat der Frage " Wie kann ich Eigenschaften von zwei JavaScript-Objekten dynamisch zusammenführen? ". Während diese Frage lediglich ein Objekt erstellen möchte, das alle Schlüssel und Werte aus zwei separaten Objekten enthält, möchte ich speziell darauf eingehen, wie dies zu tun ist, falls beide Objekte einige, aber nicht alle Schlüssel gemeinsam haben und welches Objekt Vorrang hat ( Standardeinstellung) für das resultierende Objekt für den Fall, dass doppelte Schlüssel vorhanden sind. Und noch genauer, ich wollte die Verwendung der jQuery-Methode ansprechen, um dies zu erreichen, und einen alternativen Weg finden, dies ohne jQuery zu tun. Während sich viele der Antworten auf beide Fragen überschneiden, bedeutet dies nicht, dass die Fragen selbst gleich sind.

mbeasley
quelle
8
Stehlen Sie einfach so, wie es jQuery macht james.padolsey.com/jquery/#v=1.6.2&fn=jQuery.extend :-P
Rocket Hazmat
Gute Idee @RocketHazmat, beachten Sie, wenn Sie diese Funktion kopieren, hat es ein paar andere jQuery-Abhängigkeiten wie jQuery.isPlainObjectundjQuery.isFunction
Andy Raddatz

Antworten:

131

Um das Ergebnis in Ihrem Code zu erhalten, gehen Sie wie folgt vor:

function extend(a, b){
    for(var key in b)
        if(b.hasOwnProperty(key))
            a[key] = b[key];
    return a;
}

Beachten Sie, dass die Art und Weise, wie Sie dort erweitern, das Standardobjekt ändert. Wenn Sie das nicht wollen, verwenden Sie

$.extend({}, default, config)

Eine robustere Lösung, die die Funktionalität von jQuery nachahmt, wäre folgende:

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
quelle
1
Ich würde gerne ein Codebeispiel in Aktion sehen, vielleicht Geige, weil einfaches js so viel cooler, aber so abstrakt ist, dank einer Million.
thednp
3
Dies wiederholt sich nicht, wie es $ .extend tut
ekkis
30

Sie können Object.assign verwenden.

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

Es kopiert die Werte aller aufzählbaren eigenen Eigenschaften von einem oder mehreren Quellobjekten in ein Zielobjekt und gibt das Zielobjekt zurück.

Object.assign(target, ...sources)

Es funktioniert in allen Desktop-Browsern außer IE (aber einschließlich Edge). Der mobile Support wurde gemindert.

Überzeugen Sie sich hier: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/assign

Über tiefe Kopie

Object.assign verfügt jedoch nicht über die Deep-Option, die die Erweiterungsmethode von jQuery bietet.

Hinweis: Im Allgemeinen können Sie JSON jedoch für einen ähnlichen Effekt verwenden

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"}
ling
quelle
Ja, aber hier springt es aus den Standardeinstellungen über den Schlüssel1 und fügt ihn nicht dem neuen Objekt hinzu.
Ionut Necula
@Anonymous Eigentlich fügt es es hinzu, aber es wird durch den Wert key1 aus dem Konfigurationsarray überschrieben.
Ling
Ich nahm an, dass das passiert. Es wird also nicht immerhin hinzugefügt.
Ionut Necula
4

Dies ist mein etwas anderer Ansatz mit Deep Copy, den ich mir ausgedacht habe, als ich versucht habe, eine jQuery-Abhängigkeit zu beseitigen. Es ist hauptsächlich für kleine Geräte konzipiert, sodass möglicherweise nicht alle Funktionen vorhanden sind, die man erwartet. Sollte vollständig ES5-kompatibel sein (ab IE9 aufgrund der Verwendung von 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;
  }

Sie fragen sich vielleicht, was die fünfte Zeile genau bewirkt ... Wenn obj2.key ein Objektliteral ist (dh wenn es kein gewöhnlicher Typ ist), rufen wir rekursiv darauf auf. Wenn eine Eigenschaft mit diesem Namen in obj1 noch nicht vorhanden ist, initialisieren wir sie zuerst mit einem leeren Objekt. Ansonsten setzen wir einfach obj1.key auf obj2.key.

Hier sind einige meiner Mokka / Chai-Tests, die beweisen sollten, dass die häufigsten Fälle hier funktionieren:

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',
  });
});

Ich würde mich über Feedback oder Kommentare zu dieser Implementierung freuen. Danke im Voraus!

Rico Pfaus
quelle
2

Sie können die Eigenschaften von Object mithilfe der forAnweisung durchlaufen .

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
quelle
2
Dabei werden Eigenschaften, die in nicht vorhanden sind, nicht berücksichtigt a. Obwohl dies nicht im Beispiel enthalten ist, $.extendfunktioniert es tatsächlich so, dass alle Eigenschaften aller Objekte zusammengeführt werden.
Felix Kling
1
Während ich den jquery-Basiscode lese, führt er tatsächlich eine rekursive Funktion aus, um den Wert zu kopieren oder zu klonen. Es ist also ein tiefes Klonen / Kopieren, nicht nur das Kopieren auf der ersten Ebene als obiger Code.
Aladine
2

Ich bevorzuge diesen Code, der mein generisches forEachIn verwendet und das erste Objekt nicht entstellt:

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

Wenn Sie wirklich Dinge in das erste Objekt einbinden möchten, können Sie Folgendes tun:

obj1 = extend(obj1, obj2);
GeeWhizBang
quelle
Dies ist bei weitem die beste Antwort hier. Es kopiert tief, entstellt das erste Objekt nicht, unterstützt unendliche Objekte und kann beim Erweitern Typen ändern (dh viele Antworten hier erweitern keine Zeichenfolge auf ein Objekt, diese Antwort wird es tun). Dies bekommt jedes Häkchen in meinem Buch. Vollauszug und extrem einfach zu verstehen.
Nick Steele
0

Es hilft mir sehr, wenn ich mich mit reinem Javascript entwickle.

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
quelle
0

Der Ansatz von Ivan Kuckir kann auch angepasst werden, um einen neuen Objektprototyp zu erstellen:

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
quelle