Безпечний доступ до власності (та умовне призначення) в 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.