Equivalente JavaScript del metodo di estensione di jQuery


92

sfondo

Ho una funzione che accetta un configoggetto come argomento. All'interno della funzione ho anche defaultoggetto. Ciascuno di questi oggetti contiene proprietà che essenzialmente funzionano come impostazioni per il resto del codice all'interno della funzione. Per evitare di dover specificare tutte le impostazioni all'interno configdell'oggetto, utilizzo il extendmetodo di jQuery per inserire un nuovo oggetto, settingscon qualsiasi valore predefinito defaultdall'oggetto se non fosse specificato confignell'oggetto:

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

Problema

Funziona benissimo, ma mi piacerebbe riprodurre questa funzionalità senza la necessità di jQuery. Esiste un mezzo altrettanto elegante (o simile a) per farlo con il semplice javascript?


Modifica: giustificazione non duplicata

Questa domanda non è un duplicato della domanda " Come posso unire dinamicamente le proprietà di due oggetti JavaScript? ". Considerando che quella domanda vuole semplicemente creare un oggetto che contenga tutte le chiavi e i valori di due oggetti separati, voglio specificamente affrontare come farlo nel caso in cui entrambi gli oggetti condividano alcune ma non tutte le chiavi e quale oggetto avrà la precedenza ( il valore predefinito) per l'oggetto risultante nel caso in cui siano presenti chiavi duplicate. E ancora più specificamente, volevo affrontare l'uso del metodo jQuery per raggiungere questo obiettivo e trovare un modo alternativo per farlo senza jQuery. Sebbene molte delle risposte a entrambe le domande si sovrappongano, ciò non significa che le domande stesse siano le stesse.


8
Ruba semplicemente il modo in cui fa jQuery james.padolsey.com/jquery/#v=1.6.2&fn=jQuery.extend :-P
Rocket Hazmat

Buona idea @RocketHazmat, nota che se copi la pasta quella funzione ha alcune altre dipendenze jQuery come jQuery.isPlainObjectejQuery.isFunction
Andy Raddatz

Risposte:


131

Per ottenere il risultato nel tuo codice, dovresti fare:

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

Tieni presente che il modo in cui hai utilizzato l'estensione modificherà l'oggetto predefinito. Se non lo vuoi, usa

$.extend({}, default, config)

Una soluzione più robusta che imita la funzionalità di jQuery sarebbe la seguente:

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
Vorrei vedere un esempio di codice in azione, forse violino, perché plain js è molto più interessante ma così astratto, grazie mille.
thednp

3
questo non si ripete, come fa $ .extend
ekkis

30

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

Copia i valori di tutte le proprietà enumerabili da uno o più oggetti di origine a un oggetto di destinazione e restituisce l'oggetto di destinazione.

Object.assign(target, ...sources)

Funziona in tutti i browser desktop tranne IE (ma incluso Edge). Ha mitigato il supporto mobile.

Guarda tu stesso qui: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/assign

Informazioni sulla copia profonda

Tuttavia, Object.assign non ha l'opzione profonda del metodo di estensione di jQuery.

Nota: generalmente puoi utilizzare JSON per un effetto simile

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

Sì, ma qui salta sopra la chiave1 dai valori predefiniti e non la aggiunge al nuovo oggetto.
Ionut Necula

@ Anonimo In realtà lo aggiunge, ma è sovrascritto dal valore key1 dall'array di configurazione.
ling

Suppongo che stia succedendo. Quindi, dopotutto non lo sta aggiungendo.
Ionut Necula


4

Questo è il mio approccio leggermente diverso con la copia profonda che mi è venuto in mente mentre cercavo di eliminare una dipendenza jQuery. È progettato principalmente per essere piccolo, quindi potrebbe non avere tutte le funzionalità che ci si aspetta. Dovrebbe essere completamente compatibile con ES5 (a partire da IE9 a causa dell'utilizzo di 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;
  }

Potresti chiederti cosa fa esattamente la quinta riga ... Se obj2.key è un oggetto letterale (cioè se non è un tipo ordinario) chiamiamo ricorsivamente estendere su di esso. Se una proprietà con quel nome non esiste ancora in obj1, la inizializziamo prima su un oggetto vuoto. Altrimenti impostiamo semplicemente obj1.key su obj2.key.

Ecco alcuni dei miei test mocha / chai che dovrebbero dimostrare che i casi comuni funzionano qui:

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

Sarei felice di ricevere feedback o commenti su questa implementazione. Grazie in anticipo!


2

Puoi scorrere le proprietà dell'Oggetto usando l' foristruzione.

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
Ciò non terrà conto delle proprietà che non esistono in a. Sebbene non sia nell'esempio, $.extendfunziona effettivamente in questo modo, unendo tutte le proprietà di tutti gli oggetti.
Felix Kling

1
Mentre leggo il codice di base jquery, in realtà esegue una funzione ricorsiva per copiare o clonare il valore. Quindi è profondamente clone / copia, non solo per copiare al primo livello come il codice sopra.
aladine

2

Preferisco questo codice che utilizza il mio generico forEachIn e non altera il primo oggetto:

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

Se vuoi davvero unire le cose nel primo oggetto, puoi fare:

obj1 = extend(obj1, obj2);

Questa è di gran lunga la migliore risposta qui. Fa una copia profonda, non altera il primo oggetto, supporta infiniti oggetti e può alterare i tipi durante l'estensione (cioè molte risposte qui non estenderanno una stringa a un oggetto, questa risposta lo farà). Questo ottiene ogni segno di spunta nel mio libro. sostituzione completa ed estremamente facile da capire.
Nick Steele

0

Mi aiuta molto quando sviluppo con javascript puro.

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

L'approccio di Ivan Kuckir può anche essere adattato per creare un nuovo prototipo di oggetto:

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);
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.