JavaScript equivalente del método extend de jQuery

92

Antecedentes

Tengo una función que toma un configobjeto como argumento. Dentro de la función, también tengo defaultobjeto. Cada uno de esos objetos contiene propiedades que esencialmente funcionan como configuraciones para el resto del código dentro de la función. Para evitar tener que especificar todas las configuraciones dentro del configobjeto, uso el extendmétodo de jQuery para completar un nuevo objeto, settingscon los valores predeterminados del defaultobjeto si no se especificaron en el configobjeto:

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

Esto funciona muy bien, pero me gustaría reproducir esta funcionalidad sin la necesidad de jQuery. ¿Existe un medio igualmente elegante (o cercano a) para hacer esto con javascript simple?


Editar: Justificación no duplicada

Esta pregunta no es un duplicado de la pregunta " ¿Cómo puedo fusionar propiedades de dos objetos JavaScript de forma dinámica? ". Mientras que esa pregunta simplemente quiere crear un objeto que contenga todas las claves y valores de dos objetos separados, quiero abordar específicamente cómo hacer esto en caso de que ambos objetos compartan algunas claves, pero no todas, y qué objeto tendrá prioridad ( el predeterminado) para el objeto resultante en caso de que haya claves duplicadas. E incluso más específicamente, quería abordar el uso del método de jQuery para lograr esto y encontrar una forma alternativa de hacerlo sin jQuery. Si bien muchas de las respuestas a ambas preguntas se superponen, eso no significa que las preguntas en sí sean las mismas.

mbeasley
fuente
8
Sólo robar el camino jQuery hace james.padolsey.com/jquery/#v=1.6.2&fn=jQuery.extend :-P
Rocket Hazmat
Buena idea @RocketHazmat, tenga en cuenta que si copia esa función, tiene algunas otras dependencias de jQuery como jQuery.isPlainObjectyjQuery.isFunction
Andy Raddatz

Respuestas:

131

Para obtener el resultado en su código, haría lo siguiente:

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

Tenga en cuenta que la forma en que utilizó extender allí modificará el objeto predeterminado. Si no quieres eso, usa

$.extend({}, default, config)

Una solución más robusta que imita la funcionalidad de jQuery sería la siguiente:

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
fuente
1
Me gustaría ver un ejemplo de código en acción, tal vez un violín, porque js simple es mucho más genial pero tan abstracto, un millón de gracias.
thednp
3
esto no se repite, como lo hace $ .extend
ekkis
30

Puede utilizar 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 los valores de todas las propiedades propias enumerables de uno o más objetos de origen a un objeto de destino y devuelve el objeto de destino.

Object.assign(target, ...sources)

Funciona en todos los navegadores de escritorio excepto IE (pero incluido Edge). Ha mitigado el soporte móvil.

Compruébelo usted mismo aquí: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/assign

Acerca de la copia profunda

Sin embargo, Object.assign no tiene la opción profunda que tiene el método extend de jQuery.

Nota: generalmente puede usar JSON para un efecto similar aunque

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"}
abadejo
fuente
Sí, pero aquí salta la clave1 de los valores predeterminados y no la agrega al nuevo objeto.
Ionut Necula
@Anónimo En realidad, lo agrega, pero está anulado por el valor key1 de la matriz de configuración.
ling
Supuse que eso está pasando. Entonces, no lo está agregando después de todo.
Ionut Necula
4

Este es mi enfoque ligeramente diferente con una copia profunda que se me ocurrió al intentar eliminar una dependencia de jQuery. Está diseñado principalmente para ser pequeño, por lo que es posible que no tenga todas las funciones que uno espera. Debe ser totalmente compatible con ES5 (a partir de IE9 debido al uso 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;
  }

Puede preguntarse qué hace exactamente la quinta línea ... Si obj2.key es un objeto literal (es decir, si no es de un tipo ordinario) llamamos recursivamente a extender sobre él. Si una propiedad con ese nombre aún no existe en obj1, primero la inicializamos a un objeto vacío. De lo contrario, simplemente establecemos obj1.key en obj2.key.

Estas son algunas de mis pruebas de mocha / chai que deberían demostrar que los casos comunes funcionan aquí:

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

Me alegraría recibir comentarios o sugerencias sobre esta implementación. ¡Gracias por adelantado!

Rico Pfaus
fuente
2

Puede recorrer las propiedades de Object usando fordeclaración.

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
fuente
2
Eso no tendrá en cuenta las propiedades que no existen en a. Aunque no está en el ejemplo, en $.extendrealidad funciona de esa manera, fusionando todas las propiedades de todos los objetos.
Felix Kling
1
Cuando leo el código base de jquery, en realidad hace una función recursiva para copiar o clonar el valor. Por lo tanto, es una copia / clonación profunda, no solo para copiar en el primer nivel como el código anterior.
aladine
2

Prefiero este código que usa mi forEachIn genérico y no destruye el primer objeto:

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 realmente desea fusionar cosas en el primer objeto, puede hacer:

obj1 = extend(obj1, obj2);
GeeWhizBang
fuente
Esta es, con mucho, la mejor respuesta aquí. Realiza una copia profunda, no estropea el primer objeto, admite objetos infinitos y puede alterar tipos mientras se extiende (es decir, muchas respuestas aquí no extenderán una cadena a un objeto, esta respuesta lo hará). Esto tiene todas las marcas de verificación en mi libro. reemplazo de extensión completa y extremadamente fácil de entender.
Nick Steele
0

Me ayuda mucho cuando desarrollo 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;
}
Alan Ktquez
fuente
0

El enfoque de Ivan Kuckir también se puede adaptar para crear un nuevo prototipo de objeto:

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
fuente