Эквивалент метода расширения jQuery в JavaScript


94

Задний план

У меня есть функция, которая принимает configобъект в качестве аргумента. Внутри функции у меня также есть defaultобъект. Каждый из этих объектов содержит свойства, которые, по сути, работают как настройки для остальной части кода внутри функции. Чтобы избежать необходимости указывать все параметры внутри configобъекта, я использую extendметод jQuery для заполнения нового объекта settingsлюбыми значениями по умолчанию из defaultобъекта, если они не были указаны в 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};

Проблема

Это прекрасно работает, но я хотел бы воспроизвести эту функцию без использования jQuery. Есть ли не менее элегантный (или близкий к нему) способ сделать это с помощью простого javascript?


Изменить: неповторяющееся обоснование

Этот вопрос не дублирует вопрос « Как я могу динамически объединить свойства двух объектов JavaScript? ». Принимая во внимание, что этот вопрос просто хочет создать объект, содержащий все ключи и значения из двух отдельных объектов - я специально хочу рассмотреть, как это сделать в случае, если оба объекта имеют общие ключи, но не все, и какой объект получит приоритет ( значение по умолчанию) для результирующего объекта в случае наличия повторяющихся ключей. И даже более конкретно, я хотел рассмотреть использование метода jQuery для достижения этой цели и найти альтернативный способ сделать это без jQuery. Хотя многие ответы на оба вопроса совпадают, это не означает, что сами вопросы одинаковы.


8
Просто укради способ, которым это делает jQuery james.padolsey.com/jquery/#v=1.6.2&fn=jQuery.extend :-P
Rocket Hazmat

Хорошая идея @RocketHazmat, обратите внимание, если вы копируете эту функцию, у нее есть несколько других зависимостей jQuery, таких как jQuery.isPlainObjectиjQuery.isFunction
Энди Раддац

Ответы:


133

Чтобы получить результат в своем коде, вы должны сделать:

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

Имейте в виду, что способ, которым вы использовали extension, изменит объект по умолчанию. Если вы этого не хотите, используйте

$.extend({}, default, config)

Более надежное решение, имитирующее функциональность jQuery, будет следующим:

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
Я хотел бы увидеть пример кода в действии, возможно, скрипку, потому что простой js намного круче, но настолько абстрактен, спасибо миллион.
thednp 02

3
это не рекурсивно, как $ .extend
ekkis

30

Вы можете использовать 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"}

Он копирует значения всех перечислимых собственных свойств из одного или нескольких исходных объектов в целевой объект и возвращает целевой объект.

Object.assign(target, ...sources)

Он работает во всех настольных браузерах, кроме IE (но включая Edge). Уменьшена поддержка мобильных устройств.

Убедитесь сами здесь: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/assign

О глубокой копии

Однако Object.assign не имеет глубокой опции, которая есть у метода расширения jQuery.

Примечание: вы можете обычно использовать JSON для аналогичного эффекта, хотя

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

Да, но здесь он перескакивает key1 из значений по умолчанию и не добавляет его к новому объекту.
Ionut Necula

@Anonymous На самом деле он его добавляет, но переопределяется значением key1 из массива config.
ling

Я предполагал, что это происходит. Так что это не добавление в конце концов.
Ionut Necula

29

Вы можете использовать оператор распространения ECMA 2018 в объектных литералах ...

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 для старых браузеров


2
Самый изящный и чистый способ сделать это!
Velojet 06

4

Это мой немного другой подход с глубокой копией, который я придумал, пытаясь устранить зависимость jQuery. Он в основном предназначен для того, чтобы быть маленьким, поэтому в нем могут быть не все функции, которых можно ожидать. Должен быть полностью совместим с ES5 (начиная с IE9 из-за использования 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;
  }

Вы можете задаться вопросом, что именно делает пятая строка ... Если obj2.key является литералом объекта (т.е. если это не обычный тип), мы рекурсивно вызываем для него extension. Если свойства с таким именем еще не существует в obj1, мы сначала инициализируем его пустым объектом. В противном случае мы просто устанавливаем obj1.key на obj2.key.

Вот некоторые из моих тестов mocha / chai, которые должны доказать, что общие случаи работают здесь:

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

Я буду рад обратной связи или комментариям по этой реализации. Заранее спасибо!


2

Вы можете перебирать свойства объекта с помощью 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
При этом не будут учитываться свойства, которых нет в a. Хотя этого нет в примере, на $.extendсамом деле работает именно так, объединяя все свойства всех объектов.
Felix Kling

1
Когда я читаю базовый код jquery, он фактически выполняет рекурсивную функцию для копирования или клонирования значения. Таким образом, это глубокое клонирование / копирование, а не просто копирование на первом уровне, как в приведенном выше коде.
aladine

2

Я предпочитаю этот код, который использует мой общий forEachIn и не искажает первый объект:

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

Если вы действительно хотите объединить материал в первый объект, вы можете сделать:

obj1 = extend(obj1, obj2);

Это, безусловно, лучший ответ здесь. Он выполняет глубокое копирование, не искажает первый объект, поддерживает бесконечное количество объектов и может изменять типы при расширении (т.е. многие ответы здесь не расширяют строку до объекта, этот ответ будет). Это все галочки в моей книге. полная замена расширения и чрезвычайно проста для понимания.
Ник Стил

0

Мне очень помогает, когда я разрабатываю на чистом javascript.

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

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.