Есть ли в JavaScript оператор «нулевого слияния»?

1024

Существует ли в Javascript нулевой оператор коалесцирования?

Например, в С# я могу сделать это:

String someString = null;
var whatIWant = someString ?? "Cookies!";

Лучшее приближение, которое я могу вычислить для Javascript, использует условный оператор:

var someString = null;
var whatIWant = someString ? someString : 'Cookies!';

Какая-то нехорошая ИМХО. Могу ли я лучше?

  • 9
    примечание от 2018 года: x ?? y синтаксиса теперь в статусе предложения стадии 1 - слияние нулевое
Теги:
operators
null-coalescing-operator
null-coalescing

10 ответов

1808
Лучший ответ

В качестве эквивалента JavaScript оператора С# null коалесцирования (??) используется логический OR (||):

var whatIWant = someString || "Cookies!";

Есть случаи (пояснено ниже), что поведение не будет соответствовать поведению С#, но это общий, точный способ присвоения значений по умолчанию/альтернативных значений в JavaScript.


Разъяснение

Независимо от типа первого операнда, если приведение его к булевым результатам в false, назначение будет использовать второй операнд. Остерегайтесь всех нижеприведенных случаев:

alert(Boolean(null)); // false
alert(Boolean(undefined)); // false
alert(Boolean(0)); // false
alert(Boolean("")); // false
alert(Boolean("false")); // true -- gotcha! :)

Это означает:

var whatIWant = null || new ShinyObject(); // is a new shiny object
var whatIWant = undefined || "well defined"; // is "well defined"
var whatIWant = 0 || 42; // is 42
var whatIWant = "" || "a million bucks"; // is "a million bucks"
var whatIWant = "false" || "no way"; // is "false"
  • 30
    Строки типа "false", "undefined", "null", "0", "empty", "удалено" являются истинными, поскольку они являются непустыми строками.
  • 2
    @Ates Goral: +1 и Array.prototype.forEach = Array.prototype.forEach || функция (... ЧТО?
Показать ещё 18 комментариев
58
function coalesce() {
    var len = arguments.length;
    for (var i=0; i<len; i++) {
        if (arguments[i] !== null && arguments[i] !== undefined) {
            return arguments[i];
        }
    }
    return null;
}

var xyz = {};
xyz.val = coalesce(null, undefined, xyz.val, 5);

// xyz.val now contains 5

это решение работает подобно функции коаллеции SQL, оно принимает любое количество аргументов и возвращает значение null, если ни одно из них не имеет значения. Он ведет себя как С#? оператора в том смысле, что "", false и 0 считаются NOT NULL и поэтому считаются фактическими значениями. Если вы пришли из фона .net, это будет наиболее естественное решение.

40

Если || как замена С# ?? недостаточно хорош в вашем случае, поскольку он проглатывает пустые строки и нули, вы всегда можете написать свою собственную функцию:

 function $N(value, ifnull) {
    if (value === null || value === undefined)
      return ifnull;
    return value;
 }

 var whatIWant = $N(someString, 'Cookies!');
  • 1
    alert (null || '') все еще предупреждает пустую строку, и я думаю, что мне действительно нравится, что alert ('' || 'blah') предупреждает бла, а не пустую строку - хотя это полезно знать! (+1)
  • 1
    Я думаю, что на самом деле я бы предпочел определить функцию, которая возвращает false если (строго) null / undefined и true противном случае - используя это с логическим или; это может быть более читабельным, чем многие вызовы вложенных функций. например, $N(a) || $N(b) || $N(c) || d более читабельно, чем $N($N($N(a, b), c), d) .
Показать ещё 2 комментария
15

Да, это скоро. См. Здесь предложение и статус реализации.

Это выглядит так:

x ?? y

пример

const response = {
  settings: {
    nullValue: null,
    height: 400,
    animationDuration: 0,
    headerText: '',
    showSplashScreen: false
  }
};

const undefinedValue = response.settings?.undefinedValue ?? 'some other default'; // result: 'some other default'
const nullValue = response.settings?.nullValue ?? 'some other default'; // result: 'some other default'
const headerText = response.settings?.headerText ?? 'Hello, world!'; // result: ''
const animationDuration = response.settings?.animationDuration ?? 300; // result: 0
const showSplashScreen = response.settings?.showSplashScreen ?? true; // result: false
11

Никто не упомянул здесь потенциал для NaN, который - для меня - также является нулевым значением. Итак, я думал, что добавлю свои двухценты.

Для данного кода:

var a,
    b = null,
    c = parseInt('Not a number'),
    d = 0,
    e = '',
    f = 1
;

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

var result = a || b || c || d || e || f; // result === 1

Если вы используете типичный метод coalesce, как указано здесь, вы получите c, который имеет значение: NaN

var result = coalesce(a,b,c,d,e,f); // result.toString() === 'NaN'

Ни то, ни другое из них не кажется мне правильным. В моем собственном маленьком мире логики coalesce, которая может отличаться от вашего мира, я рассматриваю undefined, null и NaN, поскольку все они являются "нуль-иш". Итак, я ожидал бы вернуть d (ноль) из метода coalesce.

Если какой-либо мозг работает как мой, и вы хотите исключить NaN, то этот метод выполнит это:

function coalesce() {
    var i, undefined, arg;

    for( i=0; i < arguments.length; i++ ) {
        arg = arguments[i];
        if( arg !== null && arg !== undefined
            && (typeof arg !== 'number' || arg.toString() !== 'NaN') ) {
            return arg;
        }
    }
    return null;
}

Для тех, кто хочет, чтобы код был как можно короче, и не возражайте против небольшой ясности, вы также можете использовать это, как это было предложено @impinball. Это использует тот факт, что NaN никогда не равен NaN. Вы можете прочитать больше об этом здесь: Почему NaN не соответствует NaN?

function coalesce() {
    var i, arg;

    for( i=0; i < arguments.length; i++ ) {
        arg = arguments[i];
        if( arg != null && arg === arg ) { //arg === arg is false for NaN
            return arg;
        }
    }
    return null;
}
  • 0
    Лучшие практики - рассматривайте аргументы как массивы, используйте NaN! == NaN ( typeof + num.toString() === 'NaN' является избыточным), num.toString() === 'NaN' текущий аргумент в переменной вместо arguments[i] .
  • 0
    @impinball, предложенное вами изменение не работает, оно возвращает NaN вместо 0 (ноль) из моего теста. Я мог бы технически удалить проверку !== 'number' так как я уже оценил, что она не null или undefined , но это имеет преимущество в том, что каждый, кто читает этот код, имеет очень четкое представление, и условие будет работать независимо от порядка. Ваши другие предложения немного сокращают код, поэтому я буду их использовать.
Показать ещё 3 комментария
6

В настоящее время нет поддержки, но процесс стандартизации JS работает над ней: https://github.com/tc39/proposal-optional-chaining

  • 3
    Это что-то немного другое: a? .B (доступ к ab, если существует), а не ?? б (если а существует, то а, иначе б).
4

остерегайтесь специфического определения null для JavaScript. в javascript есть два определения "no value". 1. Null: когда переменная имеет значение null, это означает, что она не содержит в ней данных, но переменная уже определена в коде. например:

var myEmptyValue = 1;
myEmptyValue = null;
if ( myEmptyValue === null ) { window.alert('it is null'); }
// alerts

в этом случае тип вашей переменной - это объект Object. протестируйте его.

window.alert(typeof myEmptyValue); // prints Object
  1. Undefined: когда переменная не была определена ранее в коде и, как и ожидалось, она не содержит никакого значения. например:

    if ( myUndefinedValue === undefined ) { window.alert('it is undefined'); }
    // alerts
    

если такой случай, тип вашей переменной равен 'undefined'.

обратите внимание, что если вы используете оператор сравнения типов преобразования (==), JavaScript будет действовать одинаково для обоих этих пустых значений. чтобы различать их, всегда используйте оператор строгого сравнения (===).

  • 1
    На самом деле, ноль это значение. Это специальное значение типа Object. Переменная, имеющая значение NULL, означает, что она содержит данные, данные являются ссылкой на нулевой объект. Переменная может быть определена со значением undefined в вашем коде. Это не то же самое, что переменная, которая не объявляется.
  • 0
    Фактическая разница между объявленной переменной или нет: alert (window.test) / * undefined * /; alert («тест» в окне) / * false * /; window.test = не определено; предупреждение (window.test) / * не определен * /; alert («тест» в окне) / * true * /; for (var p в окне) {/ * p может быть "test" * /}
Показать ещё 1 комментарий
3

После прочтения вашего разъяснения ответ @Ates Goral дает вам возможность выполнить ту же операцию, которую вы выполняете на С# в JavaScript.

@Ответ Gumbo обеспечивает лучший способ проверить значение null; однако важно отметить разницу в == по сравнению с === в JavaScript, особенно когда речь идет о проблемах проверки undefined и/или null.

Там действительно хорошая статья о различии в двух терминах здесь. В принципе, поймите, что если вы используете == вместо ===, JavaScript попытается объединить значения, которые вы сравниваете, и вернуть результат сравнения после этого слияния.

  • 0
    Одна вещь, которая беспокоила меня об этой статье (и Jash), это неопределенное свойство window.hello, которое по какой-то причине оценивается как null. Это должно быть неопределенным вместо этого. Попробуйте консоль ошибок Firefox и убедитесь сами.
0

Вот моя реализация elvis. Просто передайте корневой объект и цепочку в строку. Он всегда возвращает последний не ложный элемент цепочки. Работает как с объектами, так и с массивами, методами и примитивами.

elvis(myObject, 'categories.shirts[0].color');

Рабочий пример:

const elvis = (obj, keychain) => {
  const keys = keychain.split('.');
  let base;
  let prevBase;
  base = obj;

  for (let i = 0; i < keys.length; i += 1) {
    const handleArray = (parent, key) => {
      if (key.indexOf('[') > -1) {
        const arrayName = key.split('[')[0];
        const arrayIndex = +key.split('[')[1].slice(0, -1);
          return parent[arrayName] && parent[arrayName][arrayIndex];
      }
      if (key.indexOf('(') > -1) {
        const methodName = key.split('(')[0];
        return parent[methodName] && parent[methodName]();
      }      
      return parent[key];
    }
    prevBase = handleArray(base, keys[i]);

    if (!prevBase) {
      return base;
    } 
    base = prevBase;
  }
  return prevBase;
}

//--------

const myObject = {
  categories: {
    getFoo: () => 'foo',
    shirts: [
      { color: 'red' },
      { color: 'blue' }
    ]
  }
}

console.log(elvis(myObject, 'categories.shirts[0].color'));
console.log(elvis(myObject, 'categories.getFoo()'));
console.log(elvis(myObject, 'categories.getBar()'));
console.log(elvis(myObject, 'categories.shirts[0].length'));
console.log(elvis(myObject, 'categories.pans[2].color'));
0

Этот сайт предлагает живое преобразование из С# в JS и обычно помогает понять или получить ответ:

https://deck.net/af073126fca87f3d419a8943a19b87c2

Ещё вопросы

Сообщество Overcoder
Наверх
Меню