JavaScript еквівалент методу розширення jQuery


92

Передумови

У мене є функція, яка приймає 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. Хоча багато відповідей на обидва питання збігаються, це не означає, що самі питання однакові.



Хороша ідея @RocketHazmat, зауважте, якщо ви копіюєте пасту, яка функціонує, вона має кілька інших залежностей jQuery, таких як jQuery.isPlainObjectіjQuery.isFunction
Andy Raddatz

Відповіді:


131

Щоб отримати результат у своєму коді, ви повинні зробити:

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

Майте на увазі, що те, як ви використовували розширення там, змінить об’єкт за замовчуванням. Якщо ви цього не хочете, використовуйте

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

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

Так, але тут він перескакує ключ1 за замовчуванням і не додає його до нового об'єкта.
Ionut Necula,

@Anonymous Насправді він додає його, але його замінює значення key1 із масиву конфігурації.
лінг

Я припускав, що це відбувається. Отже, це не додавання його після всього.
Ionut Necula

28

Ви можете використовувати оператор поширення 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 для старих браузерів


1
Набагато найохайніший і найчистіший спосіб зробити це!
Velojet

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 - об’єктний літерал (тобто, якщо це не звичайний тип), ми рекурсивно викликаємо розширення на ньому. Якщо властивість з таким іменем ще не існує в obj1, спочатку ми ініціалізуємо його до порожнього об’єкта. В іншому випадку ми просто встановлюємо obj1.key на obj2.key.

Ось декілька моїх тестів мокко / чай, які повинні підтвердити поширені випадки роботи тут:

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насправді працює таким чином, об'єднуючи всі властивості всіх об'єктів.
Фелікс Клінг,

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.