Null-bezpieczny dostęp do właściwości (i przypisanie warunkowe) w ES6 / 2015

149

Czy nullw ES6 (ES2015 / JavaScript.next / Harmony) istnieje operator -safe dostępu do właściwości (propagacja / istnienie wartości null), na przykład ?.w CoffeeScript ? A może planowane jest na ES7?

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

Będzie to mniej więcej tak:

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

W idealnym przypadku rozwiązanie nie powinno przypisywać (nawet undefined) warunku, aThingjeśli possiblyNulljestnull

ᆼ ᆺ ᆼ
źródło
3
@naomik Ten rodzaj sprawdzania wartości null może być bardzo przydatny w przypadku instrukcji, w których sprawdza się głęboko zagnieżdżoną właściwość, np. if( obj?.nested?.property?.value )zamiastif( obj && obj.nested && obj.nested.property && obj.nested.property.value )
Sean Walsh
@SeanWalsh, jeśli twoje obiekty są tak głęboko zagnieżdżone lub jeśli twoje funkcje zagłębiają się tak głęboko w twoich obiektach, prawdopodobnie istnieje również kilka innych problemów z twoją aplikacją.
Dziękuję
1
porównać var appConfig = loadConfig(config, process.env); connect(appConfig.database);do connect(config). Można przekazać wiele prostsze do obiektu connectzamiast przechodząc cały configobiekt, można użyć conf.username, conf.passwordzamiast próbować coś podobnego config[process.env]?.database?.username, config[process.env]?.database?.password. Odniesienie: Prawo Demeter .
Dziękuję
Ponadto, jeśli wykonasz coś takiego jak ustawienie wartości domyślnych lub oczyszczenie właściwości (można to zrobić w loadConfigpowyższym przykładzie), możesz przyjąć założenia dotyczące istnienia właściwości i pominąć sprawdzanie wartości null w niezliczonych obszarach aplikacji.
Dziękuję
4
@naomik O ile język obsługuje zagnieżdżanie obiektów, jest to nadal przydatna funkcja - niezależnie od tego, co Ty lub ja myślimy o architekturze samej aplikacji. Nawiasem mówiąc, złożone wykresy obiektów, takie jak ten, są bardzo powszechne w ORMach, które modelują złożony model danych.
Sean Walsh

Odpowiedzi:

98

Aktualizacja (2020-01-31): Wygląda na to, że ludzie wciąż to odkrywają, oto aktualna historia:

Aktualizacja (2017-08-01): Jeśli chcesz użyć oficjalnej wtyczki, możesz wypróbować wersję alfa Babel 7 z nową transformacją. Twój przebieg może się różnić

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

Oryginał :

Funkcja, która spełnia to, co jest obecnie na etapie 1: Opcjonalne łączenie w łańcuch.

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

Jeśli chcesz go dzisiaj używać, dostępna jest wtyczka Babel, która to umożliwia.

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

podstawowe dni
źródło
Czy dobrze rozumiem, że nie obejmuje to jednak przypisania warunkowego? street = user.address?.streetustawiłby się streetw każdym przypadku?
ᆼ ᆺ ᆼ
1
Właściwie niestety myślę, że masz rację. Street Myślę, że zostanie przydzielony undefined. Ale przynajmniej nie rzuciłby się na próbę dostępu do właściwości na undefined.
basicdays
2
Wygląda również na to, że możesz operator po lewej stronie, a jeśli cokolwiek zmienia na niezdefiniowane, prawa ręka nie jest oceniana. Wtyczka babel może się nieco różnić, sam jej jeszcze nie testowałem.
basicdays
1
Jeśli chodzi o przypisanie warunkowe po lewej stronie =, wygląda na to, że nie jest to obecnie obsługiwane w oficjalnej specyfikacji. github.com/tc39/proposal-optional-chaining#not-supported
basicdays
1
Wygląda na to, że od maja 2020 r. Zaimplementowały to obecne przeglądarki i Typescript!
Josh Diehl
65

To nie jest tak ładne jak?. operator, ale aby osiągnąć podobny wynik, możesz zrobić:

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

Ponieważ obie wartości nulli undefinedsą wartościami fałszywymi ( zobacz to odniesienie ), dostęp do właściwości po &&operatorze jest możliwy tylko wtedy, gdy w poprzedniku nie ma wartości null lub undefined.

Alternatywnie możesz napisać taką funkcję:

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

Stosowanie:

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

Lub z wartością zastępczą:

_try(() => user.address.postcode, "none") // return postcode or a custom string
tocqueville
źródło
Racja, prawdopodobnie powinienem wyjaśnić pytanie, najważniejsze, o co mi chodziło, to zadanie warunkowe
ᆼ ᆺ ᆼ
tylko następstwo tego; aby bezpiecznie znaleźć odwrotność, możesz użyć !(user && user.address && user.address.postcode) :)
rob2d
foo && foo.bar && foo.bar.quux ...w dużej bazie kodów jest to brzydkie i powoduje wiele komplikacji, których lepiej byłoby unikać.
Skylar Saveland
1
To najczystsze rozwiązanie, jakie znalazłem w internecie. Używam tego z maszynopisem:_get<T>(func: () => T, fallbackValue?: T) :T
tomwassing
3
Jeśli user.address.postcodejest niezdefiniowane, _try(() => user.address.postcode, "")zwróci undefinedzamiast "". Więc kod _try(() => user.address.postcode, "").lengthzgłosi wyjątek.
Alexander Chen,
33

Nie. Możesz użyć lodash # get lub czegoś podobnego do tego w JavaScript.

Girafa
źródło
4
Czy są propozycje dodania go do ES7?
ᆼ ᆺ ᆼ
1
Nie spotkałem żadnego.
Girafa
3
@PeterVarga: Wiele. Za dużo dyskusji. Gramatyka nie jest łatwa dla tej funkcji
Bergi
1
lodash.getjest nieco inny, ponieważ oczywiście nie może wykonać przypisania warunkowego.
ᆼ ᆺ ᆼ
17

Alternatywa waniliowa zapewniająca bezpieczny dostęp do nieruchomości

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

Prawdopodobnie najbardziej zwięzłe przypisanie warunkowe byłoby takie

try { b = a.b.c.d.e } catch(e) {}
Yagger
źródło
Jak więc napisałbyś zadanie w pytaniu za pomocą tego mechanizmu? Czy nadal nie potrzebujesz warunku, aby nie przypisywać w przypadku, gdy possiblyNullnie jest zdefiniowany / null?
ᆼ ᆺ ᆼ
Jasne, nadal musisz sprawdzić, czy chcesz, aby zadanie się wydarzyło, czy nie. Jeśli użyjesz operatora '=', coś nieuchronnie zostanie przypisane, czy to dane, czy też nieokreślone. Więc powyższe to tylko bezpieczny dostęp do nieruchomości. Czy operator przypisania warunkowego w ogóle istnieje w jakimkolwiek języku?
Yagger
Oczywiście, na przykład CoffeeScript , ma również||=
ᆼ ᆺ ᆼ
+1 Nawet moja pierwsza myśl była (((a.b || {}).c || {}).d || {}).e. Ale bardziej precyzyjne byłoby((((a || {}).b || {}).c || {}).d || {}).e
IsmailS
6

Nie, w ES6 nie ma operatora propagacji zerowej. Będziesz musiał zastosować jeden ze znanych wzorów .

Możesz jednak użyć destrukturyzacji:

({thing: aThing} = possiblyNull);

Jest wiele dyskusji (np. Ta ) na temat dodania takiego operatora w ES7, ale żadna tak naprawdę się nie rozpoczęła.

Bergi
źródło
Wyglądało to obiecująco, ale przynajmniej to, co robi z nim Babel, nie różni się od aThing = possiblyNull.thing
zwykłej
1
@PeterVarga: Ups, masz rację, destrukturyzacja działa, gdy właściwość nie istnieje, ale nie, gdy obiekt istnieje null. Musiałbyś podać wartość domyślną, podobną do tego wzorca, ale z bardziej zagmatwaną składnią.
Bergi
4

Przechodzenie przez listę tutaj , nie ma obecnie propozycja, aby dodać bezpieczne przechodzenie do ECMAScript. Więc nie tylko nie ma na to fajnego sposobu, ale nie zostanie dodany w przewidywalnej przyszłości.

Antymon
źródło
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);
Gary Fu
źródło
1

Opcjonalne ?.łączenie łańcuchowe i zerowe łączenie??

Możesz teraz bezpośrednio użyć ?.inline, aby bezpiecznie przetestować istnienie. Obsługują ją wszystkie nowoczesne przeglądarki.

?? można użyć do ustawienia wartości domyślnej, jeśli jest ona niezdefiniowana lub zerowa.

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

Jeśli właściwość istnieje, ?.przechodzi do następnego sprawdzenia lub zwraca prawidłową wartość. Każda awaria spowoduje natychmiastowe zwarcie i powrót 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

Aby zapewnić domyślną zdefiniowaną wartość, możesz użyć ??. Jeśli potrzebujesz pierwszej prawdziwej wartości, możesz użyć ||.

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

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

Jeśli nie zaznaczysz przypadku, właściwość po lewej stronie musi istnieć. Jeśli nie, zgłosi wyjątek.

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

?.Obsługa przeglądarek - 78%, lipiec 2020 r

??Obsługa przeglądarek - 78%

Dokumentacja Mozilli

-

Logiczne przypisanie zerowe, rozwiązanie 2020+

Obecnie do przeglądarek dodaje się nowych operatorów ??=, ||=i &&=. Nie robią dokładnie tego, czego szukasz, ale mogą prowadzić do tego samego wyniku w zależności od celu twojego kodu.

UWAGA: To nie są powszechne w wersji przeglądarek publicznych jeszcze , ale powinna również transpile Babel. Zaktualizuje się wraz ze zmianą dostępności.

??=sprawdza, czy lewa strona jest niezdefiniowana lub zerowa, przerywając, jeśli jest już zdefiniowana. Jeśli nie, lewej stronie przypisywana jest wartość prawej strony. ||=i &&=są podobne, ale oparte na operatorach ||i &&.

Podstawowe przykłady

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

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

Przykłady obiektów / tablic

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" }

Wsparcie przeglądarek lipiec 2020 - 0,03%

Dokumentacja Mozilli

Gibolt
źródło
1
Pytanie dotyczy również warunkowego przydziału
ᆼ ᆺ ᆼ
Dodano kilka przykładów z ?.i ??oraz szczegółowe nadchodzące rozwiązanie, które może działać w Twojej sytuacji. Prawdopodobnie najlepsze obecne rozwiązanie to po prostu zrobićaThing = possiblyNull?.thing ?? aThing
Gibolt
0

Bezpieczna metoda głębokiego pobierania wydaje się być naturalnym rozwiązaniem dla underscore.js, ale problemem jest unikanie programowania ciągów. Modyfikowanie odpowiedzi @ Felipe, aby uniknąć programowania ciągów (lub przynajmniej odsuwa przypadki brzegowe z powrotem do dzwoniącego):

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

Przykład:

var test = { 
  a: { 
    b: 'b property value',
    c: { }
  } 
}
safeGet(test, ['a', 'b']) 
safeGet(test, "a.b".split('.'))  
prototyp
źródło
Parafrazując Ricka: to brzmi jak programowanie ciągów z dodatkowymi krokami.
tocqueville
1
lodash implementuje teraz deep get / set _.get(obj, array_or_dotstring)
prototyp
1
Ale żeby być uczciwym, nawet notacja kropek i znaków dostępu w JavaScript jest w zasadzie programowaniem ciągów, w obj.a.b.cporównaniu zobj['a']['b']['c']
prototypem
1
Skąd się bierze keys?
Levi Roberts
-2

Wiem, że jest to pytanie JavaScript, ale myślę, że Ruby obsługuje to na wszystkie wymagane sposoby, więc myślę, że jest to odpowiedni punkt odniesienia.

.&, tryi && mają swoje mocne strony i potencjalne pułapki. Świetny przegląd tych opcji tutaj: http://mitrev.net/ruby/2015/11/13/the-operator-in-ruby/

TLDR; Konkluzja rubinów jest taka, że digjest zarówno przyjemniejsza dla oczu, jak i silniejsza gwarancja, że wartość lub nullzostanie przypisana.

Oto prosta implementacja w 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
}

Może to być używane dla dowolnej głębokości zagnieżdżania i obsługuje funkcje.

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

tryPodejście jest równie miłe czytać w JS, jak pokazano w poprzednich odpowiedziach. Nie wymaga też zapętlenia, co jest jedną z wad tej implementacji.

theUtherSide
źródło
Umieściłem tę odpowiedź tutaj jako zabawne / kompletne / alternatywne rozwiązanie b / c Podoba mi się sposób, w jaki ruby ​​robi to z cukrem syntaktycznym. Po prostu przekazuję to społeczności - zagłosuj jeszcze raz, a szczęśliwie usunę ten post. Twoje zdrowie!
theUtherSide
Naprawdę fajnie jest zobaczyć, że ECMAScript2019 ma świetną propozycję dla "Opcjonalnego łańcucha " podobnego do Rubiego: github.com/tc39/proposal-optional-chaining Jest dziś dostępny za pośrednictwem wtyczki Babel i działa również do wywoływania metod na obiekcie: babeljs.io / docs / pl / babel-plugin-
offer
-5

Pomyślałem, że to pytanie wymaga trochę odświeżenia na rok 2018. Można to zrobić ładnie bez użycia bibliotek Object.defineProperty()i można z niego korzystać w następujący sposób:

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

Uważam to za bezpieczne (i js-etyczne) ze względu na definicje writeablei enumerableobecnie dostępne dla definePropertymetody Object, jak udokumentowano w MDN

definicja funkcji poniżej:

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);
    }
});

Przygotowałem plik jsBin z wyjścia konsoli do wykazania tego. Zwróć uwagę, że w wersji jsBin dodałem również niestandardowy wyjątek dla pustych wartości. Jest to opcjonalne, więc pominąłem to minimum powyżej.

Mile widziane ulepszenia

Felipe
źródło
2
Dzięki temu de facto piszesz kod w łańcuchach. To naprawdę zły pomysł. Uniemożliwia to refaktoryzację kodu i nie jest przyjazny dla środowiska IDE.
tocqueville