В Javascript, как условно добавить член к объекту?

388

Я хотел бы создать объект с условно добавленным членом. Простой подход:

var a = {};
if (someCondition)
    a.b = 5;

Теперь я хотел бы написать более идиоматический код. Я стараюсь:

a = {
    b: (someCondition? 5 : undefined)
};

Но теперь bявляется членом, aчья ценность undefined. Это не желаемый результат.

Есть ли удобное решение?

Обновить

Я ищу решение, которое могло бы обработать общий случай с несколькими участниками.

a = {
  b: (conditionB? 5 : undefined),
  c: (conditionC? 5 : undefined),
  d: (conditionD? 5 : undefined),
  e: (conditionE? 5 : undefined),
  f: (conditionF? 5 : undefined),
  g: (conditionG? 5 : undefined),
 };
viebel
источник
23
Да, есть; первый бит кода.
Паоло Бергантино
6
Не уверен, что есть такая вещь, как идиоматический JavaScript ...
Майкл Берковски
Это действительно имеет значение? Если вы никогда не определились a.b, получение a.bвернется в undefinedлюбом случае.
Теему
6
@ Temu: это может иметь значение, когда inоператор используется.
2
Пока нет возможности иметь условные свойства в литеральных объектах, но я бы хотел, чтобы они добавили их в ES7, это может быть очень удобно, особенно в программировании на стороне сервера!
Али

Ответы:

118

В чистом Javascript я не могу придумать ничего более идиоматического, чем ваш первый фрагмент кода.

Однако если об использовании библиотеки jQuery не может быть и речи, то $ .extend () должен соответствовать вашим требованиям, потому что, как сказано в документации:

Неопределенные свойства не копируются.

Поэтому вы можете написать:

var a = $.extend({}, {
    b: conditionB ? 5 : undefined,
    c: conditionC ? 5 : undefined,
    // and so on...
});

И получить ожидаемые результаты (если conditionBесть false, то bне будет существовать в a).

Фредерик Хамиди
источник
21
Есть гораздо лучший метод, который не требует jQuery. Посмотрите на ответ Джейми Хилла.
Андрей Расмуссен
13
@ Андрей, этот ответ требует ES6, которого не было в то время, когда я писал свой.
Фредерик Хамиди
работает ли null таким же образом? или это должно быть неопределенным?
Aous1000
Это на самом деле неправильный ответ, потому что он использует jQuery, и это троичное условие не удалит свойство из объекта, это просто установит свойство как неопределенное. Посмотрите ответ @lagistos для правильного способа сделать это,
Александр Ким
Да, проверьте ответ Джейми ниже: stackoverflow.com/a/40560953/10222449
MadMac
874

Я думаю, что @InspiredJW сделал это с ES5, и, как указал @trincot, лучше использовать es6. Но мы можем добавить немного больше сахара, используя оператор распространения и оценку логического И короткого замыкания:

const a = {
   ...(someCondition && {b: 5})
}
Джейми Хилл
источник
2
Я не уверен, что это правильно, говорится в предложенииNull/Undefined Are Ignored , но оно не говорит о falseтом, что оно игнорируется. Транспортировщики в настоящее время могут это разрешить, но соответствует ли это требованиям? Следующее должно быть, {...someCondition ? {b: 5} : null}но не так компактно.
Бенджамин Добелл
24
Я спросил, действительно ли это относится к людям, которые сделали предложение о распространении, и они сказали, что это хорошо. github.com/tc39/proposal-object-rest-spread/issues/45 , куб.см @BenjaminDobell
김민준
73
Оператор распространения @AlanH подобен сокращению Object.assignи имеет более низкий приоритет, чем оператор &&. Он игнорирует значение без свойства (логическое, нулевое, неопределенное, число) и добавляет все свойства объекта после того, как ...на месте. помните, что &&оператор возвращает правильное значение, если true, или false в противном случае. так что, если someConditionверно, {b : 5}будет передан к ...оператору, в результате добавления свойства bк aсо значением 5. is someConditionfalse, falseбудет передано ...оператору. в результате ничего не добавлено. это умно Я люблю это.
Феликс Брюне
11
Отличный ответ, но если поместить условие и полученный объект в скобки, это значительно улучшит читаемость этого примера. Не все помнят приоритет оператора JS наизусть.
Нейротрансмиттер
6
Единственная другая проблема - вы не можете использовать это для ложных логических значений.
ggb667
92

С EcmaScript2015 вы можете использовать Object.assign:

Object.assign(a, conditionB ? { b: 1 } : null,
                 conditionC ? { c: 2 } : null,
                 conditionD ? { d: 3 } : null);

Некоторые замечания:

Еще более лаконично

Принимая вторую точку дальше, вы могли бы сократить его следующим образом (как @Jamie указывал), а falsy значения не имеют собственных перечислимых свойств ( false, 0, NaN, null, undefined, '', за исключением document.all):

Object.assign(a, conditionB && { b: 1 },
                 conditionC && { c: 2 },
                 conditionD && { d: 3 });

trincot
источник
1
Я предпочитаю первоначальное решение: проще понять, что происходит - я не был бы уверен, что произойдет с примитивным значением (по крайней мере, не смотря на спецификацию).
Аксель Раушмайер
1
Мне нравится сокращение, которое упрощает троичную логику. Это именно то, что мне было нужно. Спасибо!
TheStherSide
80
const obj = {
   ...(condition) && {someprop: propvalue},
   ...otherprops
}

Демонстрация в реальном времени:

const obj = {
  ...(true) && {someprop: 42},
  ...(false) && {nonprop: "foo"},
  ...({}) && {tricky: "hello"},
}

console.log(obj);

Lagistos
источник
7
Хотя этот фрагмент кода может решить вопрос, в том числе объяснение действительно помогает улучшить качество вашего сообщения. Помните, что вы отвечаете на вопрос читателей в будущем, и эти люди могут не знать причин, по которым вы предлагаете код.
Ральф Стубнер
1
Что этот ответ добавляет к ответу Джейми Хилла за 2 года до этого ?
Дан Даскалеску
если cond не совпадает, это вернет undefined.
Mustkeem K
2
Это работает, и у него более приятный синтаксис, чем у любого другого ответа IMO.
Сеф Рид,
1
Краткое объяснение выглядит так: оператор распространения «...» деконструирует литерал объекта и добавляет его к «obj», например, в этом случае ... (true) && {someprop: 42}, весь термин, который должен быть деконструирован, «(true) && {someprop: 42}», в этом случае логическое значение истинно, и термин просто дает {someprop: 42}, который затем деконструируется и добавляется в obj. если вместо этого логическое значение ложно, то термин будет просто ложным, и ничто не будет деконструировано и добавлено в obj
Qiong Wu
50

Использование расширенного синтаксиса с логическим значением (как предлагается здесь) не является допустимым синтаксисом. Распространение можно использовать только с итерациями .

Я предлагаю следующее:

const a = {
   ...(someCondition? {b: 5}: {} )
}
Итай Ноам
источник
2
@HossamMourad, не должен, так как предложенный код уже использовался в ответе, опубликованном более 2 лет назад. И первое замечание неверно. Это действительный синтаксис:{...false}
trincot
1
@ Итай, это не правда; примитивные значения переносятся при использовании с синтаксисом распространения. {...false}действительный синтаксис.
Trincot
@ trincot, не могли бы вы дать ссылку?
Итай Ноам
3
EcmaScript 2018 Language Specification, раздел 12.2.6 (.8) указывает, что CopyDataPropertiesвыполняется со значением ( falseв данном случае). Это включает в себя бокс примитива (разделы 7.3.23, 7.1.13). Ссылка на MDN упоминает это «исключение» в скобках.
трино
В качестве ссылки, см. Также этот пост, где я расширяюсь на эту же тему.
трино
26

Как насчет использования расширенных свойств объекта и устанавливать свойство только в том случае, если оно истинно, например:

[isConditionTrue() && 'propertyName']: 'propertyValue'

Таким образом, если условие не выполняется, оно не создает предпочтительное свойство, и, таким образом, вы можете отменить его. Смотрите: http://es6-features.org/#ComputedPropertyNames

ОБНОВЛЕНИЕ: еще лучше следовать подходу Акселя Раушмайера в его статье в блоге об условном добавлении записей внутри литералов и массивов объектов ( http://2ality.com/2017/04/conditional-literal-entries.html ):

const arr = [
  ...(isConditionTrue() ? [{
    key: 'value'
  }] : [])
];

const obj = {
  ...(isConditionTrue() ? {key: 'value'} : {})
};

Мне очень помогло.

Дмитрий Рейфшнайдер
источник
1
Это почти сработает. Проблема в том, что он добавит дополнительный falseключ. Например, {[true && 'a']: 17, [false && 'b']: 42}это{a:17, false: 42}
viebel
3
Я нашел более сжатый способ: ...isConditionTrue() && { propertyName: 'propertyValue' }
Дмитрий
Лучший способ: ... (isConditionTrue ()? {Key: 'value'}: {})
Дмитрий Рейфшнайдер
Ссылка на блог Акселя Раушмайера дает такой ответ. Пример "... insertIf (cond, 'a')" в статье - именно то, что я искал. Спасибо
Джозеф Симпсон
21

более упрощенный,

const a = {
    ...(condition && {b: 1}) // if condition is true 'b' will be added.
}
Фахил Хан
источник
5

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

var a = new function () {
    if (conditionB)
        this.b = 5;

    if (conditionC)
        this.c = 5;

    if (conditionD)
        this.d = 5;
};
Кейси Чу
источник
5

Если вы хотите сделать это на стороне сервера (без jquery), вы можете использовать lodash 4.3.0:

a = _.pickBy({ b: (someCondition? 5 : undefined) }, _.negate(_.isUndefined));

И это работает с использованием lodash 3.10.1

a = _.pick({ b: (someCondition? 5 : undefined) }, _.negate(_.isUndefined));
Mickl
источник
Нет необходимости в lodash в ES6.
Дан Даскалеску
5
var a = {
    ...(condition ? {b: 1} : '') // if condition is true 'b' will be added.
}

Я надеюсь, что это очень эффективный способ добавить запись, основанную на условии. Для получения дополнительной информации о том, как условно добавлять записи внутри литералов объекта.

Madhankumar
источник
3
[...condition?'':['item']]это добавит строковый элемент в массив
Wayou
Чем этот ответ лучше, чем ответ Джейми Хилла годом ранее ?
Дан Даскалеску
1
@DanDascalescu Ответ Джейми Хилла лучше, чем мой ответ, я так не думал, и раньше я был скорее парнем из троичного оператора.
Мадханкумар
5

Вы можете добавить все свои неопределенные значения без каких-либо условий, а затем использовать JSON.stringifyдля их удаления все:

const person = {
  name: undefined,
  age: 22,
  height: null
}

const cleaned = JSON.parse(JSON.stringify(person));

// Contents of cleaned:

// cleaned = {
//   age: 22,
//   height: null
// }
Milad
источник
4

На это уже давно дан ответ, но, глядя на другие идеи, я придумал несколько интересных производных:

Присвойте неопределенные значения тому же свойству и удалите его впоследствии

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

var a = new function() {
    this.AlwaysPresent = 1;
    this[conditionA ? "a" : "undef"] = valueA;
    this[conditionB ? "b" : "undef"] = valueB;
    this[conditionC ? "c" : "undef"] = valueC;
    this[conditionD ? "d" : "undef"] = valueD;
    ...
    delete this.undef;
};
Роберт Коритник
источник
2

Я бы сделал это

var a = someCondition ? { b: 5 } : {};

Редактируется с помощью однострочного кода

jwchang
источник
если условие ложно, a является неопределенным, что неверно.
bingjie2680
@ bingjie2680 Это не то, что ясно , что это должно быть , когда someCondition является ложным. Я только предположил . Это может быть легко изменено с : /a = undefinedreturn { b: undefined };
jwchang
@ bingjie2680 Тогда это должно быть return {};в elseрамках
jwchang
1
Это не совсем то, что вы бы сделали ... не так ли? Я имею в виду, даже если бы вы сделали условный синтаксис таким же, как у вас, почему бы вам не использовать условный оператор? a = condition ? {b:5} : undefined;
3
Поздравляю, вы только что превратили три простые строки в 7-строчную функцию без выгоды. Нет, использование анонимной функции не является преимуществом.
2

Используя библиотеку lodash, вы можете использовать _.omitBy

var a = _.omitBy({
    b: conditionB ? 4 : undefined,
    c: conditionC ? 5 : undefined,
}, _.IsUndefined)

Это удобно, если у вас есть необязательные запросы

var a = _.omitBy({
    b: req.body.optionalA,  //if undefined, will be removed
    c: req.body.optionalB,
}, _.IsUndefined)
htafoya
источник
1

Определите переменную по letи просто назначьте новое свойство

let msg = {
    to: "hito@email.com",
    from: "hifrom@email.com",
    subject: "Contact form",    
};

if (file_uploaded_in_form) { // the condition goes here
    msg.attachments = [ // here 'attachments' is the new property added to msg Javascript object
      {
        content: "attachment",
        filename: "filename",
        type: "mime_type",
        disposition: "attachment",
      },
    ];
}

Теперь msgстало

{
    to: "hito@email.com",
    from: "hifrom@email.com",
    subject: "Contact form",
    attachments: [
      {
        content: "attachment",
        filename: "filename",
        type: "mime_type",
        disposition: "attachment",
      },
    ]
}

На мой взгляд, это очень простое и легкое решение.

SohelAhmedM
источник
0

Я думаю, что ваш первый подход к условному добавлению участников совершенно нормален. Я не совсем согласен с тем, что не хочу иметь членаb из aсо значением undefined. Достаточно просто добавить undefinedпроверку с использованием forцикла с inоператором. Но в любом случае вы можете легко написать функцию для фильтрации undefinedчленов.

var filterUndefined = function(obj) {
  var ret = {};
  for (var key in obj) {
    var value = obj[key];
    if (obj.hasOwnProperty(key) && value !== undefined) {
      ret[key] = value;
    }
  }
  return ret;
};

var a = filterUndefined({
  b: (conditionB? 5 : undefined),
  c: (conditionC? 5 : undefined),
  d: (conditionD? 5 : undefined),
  e: (conditionE? 5 : undefined),
  f: (conditionF? 5 : undefined),
  g: (conditionG? 5 : undefined),
});

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

Yunchi
источник
0

Завернуть в объект

Что-то вроде этого немного чище

 const obj = {
   X: 'dataX',
   Y: 'dataY',
   //...
 }

 const list = {
   A: true && 'dataA',
   B: false && 'dataB',
   C: 'A' != 'B' && 'dataC',
   D: 2000 < 100 && 'dataD',
   // E: conditionE && 'dataE',
   // F: conditionF && 'dataF',
   //...
 }

 Object.keys(list).map(prop => list[prop] ? obj[prop] = list[prop] : null)

Завернуть в массив

Или, если вы хотите использовать метод Jamie Hill и иметь очень длинный список условий, вы должны написать ...синтаксис несколько раз. Чтобы сделать его немного чище, вы можете просто обернуть их в массив, а затем использовать reduce()для возврата их как одного объекта.

const obj = {
  X: 'dataX',
  Y: 'dataY',
  //...

...[
  true && { A: 'dataA'},
  false && { B: 'dataB'},
  'A' != 'B' && { C: 'dataC'},
  2000 < 100 && { D: 'dataD'},
  // conditionE && { E: 'dataE'},
  // conditionF && { F: 'dataF'},
  //...

 ].reduce(( v1, v2 ) => ({ ...v1, ...v2 }))
}

Или используя map()функцию

const obj = {
  X: 'dataX',
  Y: 'dataY',
  //...
}

const array = [
  true && { A: 'dataA'},
  false &&  { B: 'dataB'},
  'A' != 'B' && { C: 'dataC'},
  2000 < 100 && { D: 'dataD'},
  // conditionE && { E: 'dataE'},
  // conditionF && { F: 'dataF'},
  //...

 ].map(val => Object.assign(obj, val))
Деди Абдилла
источник
0

Это самое краткое решение, которое я могу предложить:

var a = {};
conditionB && a.b = 5;
conditionC && a.c = 5;
conditionD && a.d = 5;
// ...
subarachnid
источник
-1

Используя библиотеку lodash, вы можете использовать _.merge

var a = _.merge({}, {
    b: conditionB ? 4 : undefined,
    c: conditionC ? 5 : undefined,
})
  1. Если условие B равно false& conditionC true, тоa = { c: 5 }
  2. Если оба условия B и условие C true, тоa = { b: 4, c: 5 }
  3. Если оба условия В и условие С выполнены false, тоa = {}
user3437231
источник
Я получаю другой результат. Я использую lodash@^4.0.0. undefinedвключены в моем случае.
JohnnyQ