Нулевой безопасный доступ к собственности (и условное присвоение) в ES6 / 2015


150

Есть ли nullв ES6 оператор безопасного доступа к свойствам (нулевое распространение / существование) (например, ?.в CoffeeScript, например, в CoffeeScript) ? Или это запланировано на ES7?

var aThing = getSomething()
...
aThing = possiblyNull?.thing

Это будет примерно так:

if (possiblyNull != null) aThing = possiblyNull.thing

В идеале решение не должно назначать (даже undefined) aThingесли possiblyNullестьnull


3
@naomik Этот вид нулевой проверки может быть очень полезен для операторов if, где вы проверяете глубоко вложенное свойство, например, if( obj?.nested?.property?.value )вместоif( obj && obj.nested && obj.nested.property && obj.nested.property.value )
Шон Уолш

@SeanWalsh, если ваши объекты глубоко вложены или если ваши функции так глубоко копаются в ваших объектах, возможно, в вашем приложении есть и другие проблемы.
Спасибо,

1
сравнить var appConfig = loadConfig(config, process.env); connect(appConfig.database);с connect(config). Вы можете передать гораздо более простой объект , connectа не передавая весь configобъект, вы можете использовать conf.username, conf.passwordвместо того чтобы попытаться что - то подобное config[process.env]?.database?.username, config[process.env]?.database?.password. Ссылка: Закон Деметры .
спасибо

Кроме того, если вы делаете что-то вроде установки значений по умолчанию или дезинфекции свойств (это можно сделать в loadConfigприведенном выше примере), вы можете сделать предположения о существовании свойств и пропустить проверку нуля в бесчисленных областях вашего приложения.
Спасибо,

4
@naomik Пока язык поддерживает вложение объектов, это все еще полезная функция - независимо от того, что вы или я думаем об архитектуре самого приложения. Кроме того, подобные графы сложных объектов очень распространены в ORM, которые моделируют сложную модель данных.
Шон Уолш

Ответы:


100

Обновление (2020-01-31): Кажется, что люди все еще находят это, вот текущая история:

Обновление (2017-08-01): Если вы хотите использовать официальный плагин, вы можете попробовать альфа-сборку Babel 7 с новым преобразованием. Ваш пробег может отличаться

https://www.npmjs.com/package/babel-plugin-transform-optional-chaining

Оригинал :

Достигается функция, которая в настоящее время находится на этапе 1: необязательная цепочка.

https://github.com/tc39/proposal-optional-chaining

Если вы хотите использовать его сегодня, есть плагин Babel, который выполняет это.

https://github.com/davidyaha/ecmascript-optionals-proposal


Правильно ли я понимаю, что это не включает условное присвоение? street = user.address?.streetустановил бы streetв любом случае?
ᆼ ᆺ ᆼ

1
На самом деле, к сожалению, я думаю, что вы правы. Street Я думаю будет назначен undefined. Но, по крайней мере, это не приведет к попытке получить доступ к свойствам в undefined.
17

2
Выглядит так, как будто вы можете быть оператором с левой стороны, и если он укажет что-либо на неопределенное, правая рука не оценивается. Плагин babel может немного отличаться, я сам пока не тестировал его.
17

1
Что касается условного присваивания в левой части =, похоже, что это не поддерживается в официальной спецификации в настоящее время. github.com/tc39/proposal-optional-chaining#not-supported
basicdays

1
По состоянию на май 2020 года, похоже, что современные браузеры и Typescript реализовали это!
Джош Дил

66

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

user && user.address && user.address.postcode

Так как nullи undefinedоба являются ложными значениями ( см. Эту ссылку ), свойство после &&оператора доступно только в том случае, если прецедент не является нулевым или неопределенным.

В качестве альтернативы, вы можете написать такую ​​функцию:

function _try(func, fallbackValue) {
    try {
        var value = func();
        return (value === null || value === undefined) ? fallbackValue : value;
    } catch (e) {
        return fallbackValue;
    }
}

Использование:

_try(() => user.address.postcode) // return postcode or undefined 

Или с запасным значением:

_try(() => user.address.postcode, "none") // return postcode or a custom string

Правильно, наверное, мне следует уточнить вопрос, главное, за чем я следовал, это условное задание
ᆼ ᆺ ᆼ

просто следствие этого; чтобы найти обратное безопасно, вы можете использовать !(user && user.address && user.address.postcode) :)
rob2d

foo && foo.bar && foo.bar.quux ...в большой кодовой базе это уродливо и добавляет много сложности, которую вам лучше избегать.
Skylar Saveland

1
Это самое чистое решение, которое я нашел в интернете. Я использую это с машинописью:_get<T>(func: () => T, fallbackValue?: T) :T
tomwassing

3
Если user.address.postcodeне определено, _try(() => user.address.postcode, "")вернется undefinedвместо "". Так что код _try(() => user.address.postcode, "").lengthвызовет исключение.
Александр Чен

33

Нет. Вы можете использовать lodash # get или что-то подобное для этого в JavaScript.


4
Есть предложения добавить его в ES7?
ᆼ ᆺ ᆼ

1
Не встречал никого.
Жирафа

3
@PeterVarga: многие. Слишком много дискуссий. Грамматика не легкая вещь для этой функции
Берги

1
lodash.getнемного отличается тем, что не может выполнять условное присваивание.
ᆼ ᆺ ᆼ

17

Альтернатива ванили для безопасного доступа к собственности

(((a.b || {}).c || {}).d || {}).e

Наиболее кратким условным заданием, вероятно, будет

try { b = a.b.c.d.e } catch(e) {}

Итак, как бы вы написали назначение в вопросе, используя этот механизм? Разве вам все еще не нужно условие, чтобы не назначать, если possiblyNullне определено / null?
ᆼ ᆺ ᆼ

Конечно, вам все еще нужно проверить, хотите ли вы, чтобы назначение произошло или нет. Если вы используете оператор '=', что-то неизбежно будет присвоено, будь то данные, неопределенное значение или ноль. Таким образом, вышесказанное является просто безопасным доступом к собственности. Оператор условного присваивания существует даже на каком-либо языке?
Яггер

Конечно, например, CoffeeScript , он также имеет||=
ᆼ ᆺ ᆼ

+1 Даже моя первая мысль была (((a.b || {}).c || {}).d || {}).e. Но точнее было бы((((a || {}).b || {}).c || {}).d || {}).e
IsmailS

6

Нет, в ES6 нет нулевого оператора распространения. Вам придется пойти с одним из известных шаблонов .

Вы можете использовать деструктуризацию, хотя:

({thing: aThing} = possiblyNull);

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


Это выглядит многообещающим, однако , по крайней мере , что Бабель делает с ним ничем не отличается от всегоaThing = possiblyNull.thing
ᆼ ᆺ ᆼ

1
@PeterVarga: Ой, вы правы, деструктурирование работает, когда свойство не существует, но не когда объект существует null. Вам нужно будет указать значение по умолчанию, очень похожее на этот шаблон, но с более запутанным синтаксисом.
Берги

4

Судя по списку здесь , в настоящее время нет предложений по добавлению безопасного обхода в Ecmascript. Так что не только нет хорошего способа сделать это, но он не будет добавлен в обозримом будущем.


1
// Typescript
static nullsafe<T, R>(instance: T, func: (T) => R): R {
    return func(instance)
}

// Javascript
function nullsafe(instance, func) {
    return func(instance);
};

// use like this
const instance = getSomething();
let thing = nullsafe(instance, t => t.thing0.thing1.thingx);

1

Опциональное сцепление ?.и нулевое слияние??

Теперь вы можете напрямую использовать ?.inline для безопасного тестирования на существование. Все современные браузеры поддерживают это.

?? может использоваться для установки значения по умолчанию, если оно не определено или равно нулю.

aThing = possiblyNull ?? aThing
aThing = a?.b?.c ?? possiblyNullFallback ?? aThing

Если свойство существует, ?.переходит к следующей проверке или возвращает действительное значение. Любой сбой сразу же закоротит и вернет undefined.

const example = {a: ["first", {b:3}, false]}

example?.a  // ["first", {b:3}, false]
example?.b  // undefined

example?.a?.[0]     // "first"
example?.a?.[1]?.a  // undefined
example?.a?.[1]?.b  // 3

domElement?.parentElement?.children?.[3]?.nextElementSibling

null?.()                // undefined
validFunction?.()       // result
(() => {return 1})?.()  // 1

Чтобы обеспечить значение по умолчанию, вы можете использовать ?? . Если вам требуется первое истинное значение, вы можете использовать ||.

example?.c ?? "c"  // "c"
example?.c || "c"  // "c"

example?.a?.[2] ?? 2  // false
example?.a?.[2] || 2  // 2

Если вы не проверяете регистр, свойство левой стороны должно существовать. Если нет, он выдаст исключение.

example?.First         // undefined
example?.First.Second  // Uncaught TypeError: Cannot read property 'Second' of undefined

?.Поддержка браузеров - 78%, июль 2020 г.

?? Поддержка браузера - 78%

Документация Mozilla

-

Логическое нулевое назначение, решение 2020+

Новые операторы в настоящее время добавляются в браузеры ??=, ||=и &&=. Они не делают то, что вы ищете, но могут привести к одному и тому же результату в зависимости от цели вашего кода.

Примечание: Это не распространено в версиях браузера открытых еще , но Бабель должен transpile хорошо. Будет обновляться по мере изменения доступности.

??=проверяет, является ли левая сторона неопределенной или нулевой, короткое замыкание, если уже определено. Если нет, то левой стороне присваивается значение правой стороны. ||=и &&=похожи, но на основе ||и &&операторов.

Основные примеры

let a          // undefined
let b = null
let c = false

a ??= true  // true
b ??= true  // true
c ??= true  // false

Примеры объектов / массивов

let x = ["foo"]
let y = { foo: "fizz" }

x[0] ??= "bar"  // "foo"
x[1] ??= "bar"  // "bar"

y.foo ??= "buzz"  // "fizz"
y.bar ??= "buzz"  // "buzz"

x  // Array [ "foo", "bar" ]
y  // Object { foo: "fizz", bar: "buzz" }

Поддержка браузеров июль 2020 г. - .03%

Документация Mozilla


1
Вопрос также об условном назначении
ᆼ ᆺ ᆼ

Добавлено несколько примеров с ?.и ??, и подробное предстоящее решение, которое может работать в вашей ситуации. Лучшее текущее решение, вероятно, просто сделатьaThing = possiblyNull?.thing ?? aThing
Gibolt

0

Безопасный метод глубокого получения выглядит как естественная подгонка для underscore.js, но здесь проблема состоит в том, чтобы избегать строкового программирования. Измените ответ @ Felipe, чтобы избежать программирования строк (или, по крайней мере, отодвинуть крайние случаи назад к вызывающей стороне):

function safeGet(obj, props) {
   return (props.length==1) ? obj[keys[0]] :safeGet(obj[props[0]], props.slice(1))
}

Пример:

var test = { 
  a: { 
    b: 'b property value',
    c: { }
  } 
}
safeGet(test, ['a', 'b']) 
safeGet(test, "a.b".split('.'))  

Перефразируя Рика: это звучит как строковое программирование с дополнительными шагами.
Токвиль

1
Теперь в lodash реализован глубокий get / set _.get(obj, array_or_dotstring)
прототип

1
Но чтобы быть справедливыми даже Javascript точки и строки аксессоры обозначения в основном строка программирование, obj.a.b.cпротивobj['a']['b']['c']
прототип

1
Откуда keysидет?
Леви Робертс

-2

Я знаю, что это вопрос JavaScript, но я думаю, что Ruby справляется с этим всеми запрошенными способами, поэтому я думаю, что это актуальная точка отсчета.

.&, tryи && имеют свои сильные стороны и потенциальные подводные камни. Здесь вы найдете множество вариантов: http://mitrev.net/ruby/2015/11/13/the-operator-in-ruby/

TLDR; Вывод Рубисты что digи легче на глазах и более сильной гарантией того, что значение или nullбудет назначен.

Вот простая реализация в TypeScript:

export function dig(target: any, ...keys: Array<string>): any {
  let digged = target
  for (const key of keys) {
    if (typeof digged === 'undefined') {
      return undefined // can also return null or a default value
    }
    if (typeof key === 'function') {
      digged = key(digged)
    } else {
      digged = digged[key]
    }
  }
  return digged
}

Это может быть использовано для любой глубины вложенности и функций функций.

a = dig(b, 'c', 'd', 'e');
foo = () => ({});
bar = dig(a, foo, 'b', 'c')

tryПодход одинаково приятно читать в JS, как показано в предыдущих ответах. Это также не требует зацикливания, что является одним из недостатков этой реализации.


Я поставил этот ответ здесь как забавное / полное / альтернативное решение. Мне нравится, как ruby ​​делает это с синтаксическим сахаром. Просто разместите это для сообщества. Проголосуйте еще раз, и я с удовольствием удалю этот пост. Ура!
TheStherSide

Действительно здорово видеть, что у ECMAScript2019 есть отличное предложение для Ruby-подобного «необязательного связывания»: github.com/tc39/proposal-optional-chaining. Сегодня он доступен через плагин Babel, и он также работает для вызова методов объекта: babeljs.io / docs / ru / babel-plugin-Предложение-опционально-цепочка
theUtherSide

-5

Я думал, что этот вопрос нуждается в небольшом обновлении для 2018. Это может быть сделано красиво без использования каких-либо библиотек Object.defineProperty()и может быть использовано следующим образом:

myVariable.safeGet('propA.propB.propC');

Я считаю это безопасным (и JS-этическим) из-за writeableи enumerableопределения, теперь доступные для definePropertyметода Object, как описано в MDN

определение функции ниже:

Object.defineProperty(Object.prototype, 'safeGet', { 
    enumerable: false,
    writable: false,
    value: function(p) {
        return p.split('.').reduce((acc, k) => {
            if (acc && k in acc) return acc[k];
            return undefined;
        }, this);
    }
});

Я собрал jsBin с выводом на консоль, чтобы продемонстрировать это. Обратите внимание, что в версии jsBin я также добавил пользовательское исключение для пустых значений. Это необязательно, и поэтому я оставил это из минимального определения выше.

Улучшения приветствуются


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