Akses properti aman (dan tugas bersyarat) di ES6 / 2015

149

Apakah ada nulloperator properti akses -safe (propagasi nol / keberadaan) di ES6 (ES2015 / JavaScript.next / Harmony) seperti ?.di CoffeeScript misalnya? Atau apakah direncanakan untuk ES7?

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

Ini kira-kira akan seperti:

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

Idealnya solusinya tidak boleh menetapkan (datar undefined) aThingjika possiblyNulladanull

ᆼ ᆺ ᆼ
sumber
3
@naomik Pengecekan nol semacam ini dapat sangat berguna untuk pernyataan jika Anda memeriksa properti yang sangat bersarang, misalnya if( obj?.nested?.property?.value )alih-alihif( obj && obj.nested && obj.nested.property && obj.nested.property.value )
Sean Walsh
@SeanWalsh jika objek Anda bersarang terlalu dalam, atau jika fungsi Anda menggali terlalu dalam pada objek Anda, mungkin ada beberapa masalah lain dengan aplikasi Anda juga.
Terima kasih
1
dibandingkan var appConfig = loadConfig(config, process.env); connect(appConfig.database);dengan connect(config). Anda dapat melewati objek yang lebih sederhana untuk connectbukan lewat seluruh configobjek, Anda dapat menggunakan conf.username, conf.passwordbukan mencoba sesuatu seperti config[process.env]?.database?.username, config[process.env]?.database?.password. Referensi: Hukum Demeter .
Terima kasih
Juga, jika Anda melakukan sesuatu seperti menyetel bawaan atau membersihkan properti (ini bisa dilakukan pada loadConfigcontoh di atas), Anda dapat membuat asumsi tentang keberadaan properti dan melewatkan pemeriksaan nol di area tak terhitung dari aplikasi Anda.
Terima kasih
4
@naomik Selama bahasa mendukung objek bersarang, itu masih fitur yang berguna - terlepas dari apa yang Anda atau saya pikirkan tentang arsitektur aplikasi itu sendiri. Selain itu, grafik objek kompleks seperti ini sangat umum di ORM yang memodelkan model data yang kompleks.
Sean Walsh

Jawaban:

98

Pembaruan (2020-01-31): Sepertinya orang masih menemukan ini, inilah kisahnya saat ini:

Pembaruan (2017-08-01): Jika Anda ingin menggunakan plugin resmi, Anda dapat mencoba alpha build Babel 7 dengan transformasi baru. Jarak tempuh Anda mungkin beragam

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

Asli :

Fitur yang mencapai yang saat ini dalam tahap 1: Chaining Opsional.

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

Jika Anda ingin menggunakannya hari ini, ada plugin Babel yang menyelesaikannya.

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

hari dasar
sumber
Apakah saya mengerti benar bahwa itu tidak termasuk tugas bersyarat? street = user.address?.streetakan diatur streetdalam hal apa pun?
ᆼ ᆺ ᆼ
1
Sayangnya saya pikir Anda benar. Street Saya pikir akan ditugaskan undefined. Tapi setidaknya itu tidak mau mencoba mengakses properti di undefined.
dasar
2
Sepertinya Anda juga bisa menjadi operator di tangan kiri, dan jika tidak ada yang perlu ditentukan, tangan kanan tidak dievaluasi. Plugin babel mungkin sedikit berbeda, belum mengujinya sendiri.
dasar
1
Mengenai penugasan bersyarat di sisi kiri =, sepertinya itu tidak didukung dalam spesifikasi resmi saat ini. github.com/tc39/proposal-optional-chaining#not-supported
basicdays
1
Pada Mei 2020, sepertinya peramban dan naskah saat ini telah menerapkan ini!
Josh Diehl
65

Ini tidak sebaik? operator, tetapi untuk mencapai hasil yang serupa dapat Anda lakukan:

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

Karena nulldan undefinedkeduanya nilai-nilai palsu ( lihat referensi ini ), properti setelah &&operator hanya diakses jika presedennya tidak nol atau tidak terdefinisi.

Atau, Anda dapat menulis fungsi seperti ini:

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

Pemakaian:

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

Atau, dengan nilai cadangan:

_try(() => user.address.postcode, "none") // return postcode or a custom string
tocqueville
sumber
Benar mungkin saya harus mengklarifikasi pertanyaan, hal utama yang saya cari adalah penugasan bersyarat
ᆼ ᆺ ᆼ
hanya akibat wajar dari itu; untuk menemukan invers dengan aman, Anda dapat menggunakan !(user && user.address && user.address.postcode) :)
rob2d
foo && foo.bar && foo.bar.quux ...dalam basis kode besar ini jelek dan menambahkan banyak kerumitan yang sebaiknya Anda hindari.
Skylar Saveland
1
Ini adalah solusi terbersih yang saya temukan di internet. Saya menggunakan ini dengan naskah:_get<T>(func: () => T, fallbackValue?: T) :T
tomwassing
3
Jika user.address.postcodetidak terdefinisi, _try(() => user.address.postcode, "")akan kembali undefinedbukan "". Jadi kodenya _try(() => user.address.postcode, "").lengthakan memunculkan eksepsi.
Alexander Chen
33

Tidak. Anda dapat menggunakan lodash # get atau sesuatu seperti itu untuk ini dalam JavaScript.

Girafa
sumber
4
Apakah ada proposal untuk ditambahkan ke ES7?
ᆼ ᆺ ᆼ
1
Belum pernah menemukan.
Girafa
3
@ PeterVarga: Banyak. Terlalu banyak diskusi. Tata bahasa bukan hal yang mudah untuk fitur ini
Bergi
1
lodash.getsedikit berbeda dalam obi yang tidak dapat melakukan tugas bersyarat.
ᆼ ᆺ ᆼ
17

Alternatif vanila untuk akses properti aman

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

Tugas bersyarat yang paling ringkas mungkin adalah ini

try { b = a.b.c.d.e } catch(e) {}
belati
sumber
Jadi bagaimana Anda menulis tugas dalam pertanyaan menggunakan mekanisme ini? Apakah Anda masih membutuhkan persyaratan untuk tidak menetapkan jika possiblyNulltidak ditentukan / null?
ᆼ ᆺ ᆼ
Tentu, Anda masih perlu memeriksa apakah Anda ingin tugas terjadi atau tidak. Jika Anda menggunakan operator '=', sesuatu pasti akan diberikan, baik itu data, tidak terdefinisi atau nol. Jadi di atas hanyalah akses properti yang aman. Apakah operator penugasan bersyarat bahkan ada, dalam bahasa apa pun?
Yagger
Tentu saja, misalnya CoffeeScript , ia juga memiliki||=
ᆼ ᆺ ᆼ
+1 Bahkan pikiran pertama saya adalah (((a.b || {}).c || {}).d || {}).e. Tetapi akan lebih tepat((((a || {}).b || {}).c || {}).d || {}).e
IsmailS
6

Tidak, tidak ada operator propagasi nol di ES6. Anda harus menggunakan salah satu pola yang dikenal .

Anda mungkin dapat menggunakan destrrukturisasi, meskipun:

({thing: aThing} = possiblyNull);

Ada banyak diskusi (misalnya ini ) untuk menambahkan operator seperti itu di ES7, tetapi tidak ada yang benar-benar berjalan.

Bergi
sumber
Itu tampak menjanjikan, namun setidaknya apa yang Babel lakukan dengan itu tidak berbeda dengan hanyaaThing = possiblyNull.thing
ᆼ ᆺ ᆼ
1
@PeterVarga: Ups, Anda benar, merusak berfungsi ketika properti tidak ada, tetapi tidak ketika objeknya ada null. Anda harus memberikan nilai default, cukup banyak seperti pola ini tetapi dengan sintaks yang lebih membingungkan.
Bergi
4

Pergi dengan daftar di sini , saat ini tidak ada proposal untuk menambahkan traversal aman ke Ecmascript. Jadi tidak hanya tidak ada cara yang baik untuk melakukan ini, tetapi itu tidak akan ditambahkan di masa mendatang.

Antimon
sumber
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
sumber
1

Chaining Opsional ?.dan Penggabungan Nullish??

Anda sekarang dapat langsung menggunakan ?.inline untuk menguji keberadaan dengan aman. Semua browser modern mendukungnya.

?? dapat digunakan untuk menetapkan nilai default jika tidak terdefinisi atau nol.

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

Jika ada properti, ?.lanjutkan ke pemeriksaan berikutnya, atau mengembalikan nilai yang valid. Kegagalan akan segera terjadi korsleting dan kembali 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

Untuk memastikan nilai standar yang ditentukan, Anda dapat menggunakan ??. Jika Anda membutuhkan nilai kebenaran pertama, Anda dapat menggunakannya ||.

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

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

Jika Anda tidak memeriksa kasus, properti sisi kiri harus ada. Jika tidak, itu akan melempar pengecualian.

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

?.Dukungan Browser - 78%, Juli 2020

??Dukungan Browser - 78%

Dokumentasi Mozilla

-

Penugasan nullish yang logis, solusi 2020+

Operator baru sedang ditambahkan ke browser ??=,, ||=dan &&=. Mereka tidak melakukan apa yang Anda cari, tetapi dapat menghasilkan hasil yang sama tergantung pada tujuan kode Anda.

CATATAN: Ini tidak umum di versi browser publik belum , tetapi Babel harus transpile dengan baik. Akan diperbarui jika ketersediaan berubah.

??=memeriksa apakah sisi kiri tidak terdefinisi atau nol, korsleting jika sudah ditentukan. Jika tidak, sisi kiri diberi nilai sisi kanan. ||=dan &&=serupa, tetapi didasarkan pada ||dan &&operator.

Contoh dasar

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

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

Contoh Obyek / Array

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

Dukungan Browser Juli 2020 - .03%

Dokumentasi Mozilla

Gibolt
sumber
1
Pertanyaannya juga tentang penugasan bersyarat
ᆼ ᆺ ᆼ
Menambahkan beberapa contoh dengan ?.dan ??, dan solusi mendatang yang terperinci yang mungkin cocok untuk situasi Anda. Solusi terbaik saat ini mungkin hanya lakukanaThing = possiblyNull?.thing ?? aThing
Gibolt
0

Metode deep deep get sepertinya cocok untuk underscore.js tetapi ada masalah menghindari pemrograman string. Memodifikasi jawaban @ Felipe untuk menghindari pemrograman string (atau setidaknya mendorong kasus tepi kembali ke pemanggil):

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

Contoh:

var test = { 
  a: { 
    b: 'b property value',
    c: { }
  } 
}
safeGet(test, ['a', 'b']) 
safeGet(test, "a.b".split('.'))  
prototipe
sumber
Mengutip Rick: ini hanya terdengar seperti pemrograman string dengan langkah ekstra.
tocqueville
1
lodash sekarang mengimplementasikan deep get / set _.get(obj, array_or_dotstring)
prototype
1
Tetapi untuk bersikap adil bahkan notasi Javascript dot dan string pada dasarnya adalah pemrograman string, obj.a.b.cvsobj['a']['b']['c']
prototipe
1
Dari mana keysdatangnya?
Levi Roberts
-2

Saya tahu ini adalah pertanyaan JavaScript, tapi saya pikir Ruby menangani ini dengan semua cara yang diminta, jadi saya pikir itu adalah titik referensi yang relevan.

.&,, try& && memiliki kekuatan dan potensi jebakan mereka. Hebatnya opsi di sini: http://mitrev.net/ruby/2015/11/13/the-operator-in-ruby/

TLDR; Kesimpulan Rubyists adalah bahwa diglebih mudah di mata dan jaminan yang lebih kuat bahwa nilai atau nullakan diberikan.

Berikut ini adalah penerapan sederhana dalam 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
}

Ini dapat digunakan untuk setiap kedalaman fungsi persarangan dan pegangan.

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

The trypendekatan adalah sama-sama baik untuk membaca di JS, seperti yang ditunjukkan dalam jawaban sebelumnya. Ini juga tidak memerlukan perulangan, yang merupakan salah satu kelemahan dari implementasi ini.

sisi lain
sumber
Saya menempatkan jawaban ini di sini sebagai solusi yang menyenangkan / lengkap / alternatif b / c Saya suka cara ruby ​​melakukan ini dengan gula sintaksis. Hanya menempatkan ini di luar sana untuk komunitas - Pilih sekali lagi dan saya akan dengan senang hati menghapus posting ini. Bersulang!
theUtherSide
Sangat keren melihat ECMAScript2019 memiliki proposal yang bagus untuk Ruby-like "Optional Chaining": github.com/tc39/proposal-optional-chaining Tersedia hari ini melalui Babel Plugin, dan berfungsi untuk memanggil metode pada objek juga: babeljs.io / docs / en / babel-plugin-proposal-opsional-chaining
theUtherSide
-5

Saya pikir pertanyaan ini membutuhkan sedikit penyegaran untuk tahun 2018. Ini dapat dilakukan dengan baik tanpa menggunakan perpustakaan Object.defineProperty()dan dapat digunakan sebagai berikut:

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

Saya menganggap ini aman (dan js-etika) karena writeabledan enumerabledefinisi sekarang tersedia untuk definePropertymetode Object, seperti yang didokumentasikan dalam MDN

definisi fungsi di bawah ini:

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

Saya telah mengumpulkan jsBin dengan output konsol untuk menunjukkan ini. Perhatikan bahwa dalam versi jsBin saya juga menambahkan pengecualian khusus untuk nilai kosong. Ini opsional, jadi saya tidak menggunakan definisi minimal di atas.

Perbaikan disambut

Felipe
sumber
2
Dengan ini, Anda secara de facto menulis kode ke string. Benar-benar ide buruk untuk melakukan itu. Itu membuat kode Anda tidak mungkin untuk refactor dan tidak ramah-IDE.
tocqueville