Безпечний доступ до власності (та умовне призначення) в ES6 / 2015


149

Чи є nullв ES6 (ES2015 / JavaScript.next / Harmony) оператор доступу до власних ресурсів (безпечний розповсюдження / існування), як, наприклад, ?.у CoffeeScript ? Або планується це на ES7?

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

Це буде приблизно так:

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

В ідеалі рішення не повинно призначати (навіть undefined), aThingякщо possiblyNullєnull


3
@naomik Цей вид нульової перевірки може бути дуже корисним, якщо заяви, де ви перевіряєте властивість глибоко вкладених, наприклад, if( obj?.nested?.property?.value )замістьif( obj && obj.nested && obj.nested.property && obj.nested.property.value )
— Sean Walsh

@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, які моделюють складну модель даних.
— Шон Уолш

Відповіді:


98

Оновлення (2020-01-31): Здається, люди все ще знаходять це, ось поточна історія:

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

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

Оригінал :

Функція, яка зараз виконується на етапі 1: Необов'язкове ланцюжок.

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

Якщо ви хочете використовувати його сьогодні, є плагін Babel, який виконує це.

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


Я правильно розумію, що це не включає умовне призначення? street = user.address?.streetвстановив би streetв будь-якому випадку?
— ᆼ ᆺ ᆼ

1
Насправді, на жаль, я думаю, що ти маєш рацію. Street Думаю , призначили б undefined. Але принаймні це не буде намагатися отримати доступ до властивостей на невизначеному.
— основні дні

2
Схоже, ви можете скористатися оператором на лівій руці, і якщо це щось не визначить, права справа не оцінюється. Мобільний плагін може дещо відрізнятися, ще не дуже його тестував.
— основні дні

1
Що стосується умовного призначення на лівій частині вікна =, схоже, це не підтримується в офіційній специфікації на даний момент. github.com/tc39/proposed-optional-chaining#not-supported
— basicdays

1
Станом на травень 2020 року, схоже, що поточні браузери та Typescript це реалізували!
— Джош Діель

65

Це не так приємно, як ?. оператора, але для досягнення подібного результату ви можете зробити:

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

Щоб забезпечити визначене за замовчуванням значення, ви можете використовувати ??. Якщо вам потрібно перше значення truthy, ви можете використовувати ||.

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
— Гібольт

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('.'))  

Перефразовуючи Ріка: це просто звучить як програмування рядків із додатковими кроками.
— toqueque

1
lodash тепер реалізує глибокий get / set _.get(obj, array_or_dotstring)
— прототип

1
Але якщо бути справедливим, навіть позначення точок та рядків аксесуарів Javascript - це в основному рядкове програмування, obj.a.b.cпротиobj['a']['b']['c']
— прототип

1
Звідки keysбереться?
— Леві Робертс

-2

Я знаю, що це питання JavaScript, але я думаю, що Рубі вирішує це всіма запитуваними способами, тому я думаю, що це відповідна точка відліку.

.&, 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, як показано в попередніх відповідях. Він також не вимагає циклічного циклу, що є одним з недоліків цієї реалізації.


Я ставлю цю відповідь тут як веселе / ​​повне / альтернативне рішення b / c Мені подобається, як рубін робить це з синтаксичним цукром. Просто виклавши це для спільноти - ще раз проголосуйте, і я з радістю видалю це повідомлення. Ура!
— thetherSide

Дуже приємно бачити, що ECMAScript2019 має чудову пропозицію щодо "необов'язкового ланцюжка ", подібного до Ruby: github.com/tc39/proposed-optional-chaining Доступний сьогодні через плагін Babel, і він також працює для виклику методів на об'єкті: babeljs.io / docs / en / babel-plugin-
— offer

-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.
— toqueque
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.