Apakah ada operator null-coalescing (Elvis) atau operator navigasi aman dalam javascript?

210

Saya akan menjelaskan dengan contoh:

Operator Elvis (?:)

"Operator Elvis" adalah kependekan dari operator ternary Jawa. Salah satu contoh di mana hal ini berguna adalah untuk mengembalikan nilai 'sensible default' jika ekspresi diputuskan menjadi false atau null. Contoh sederhana mungkin terlihat seperti ini:

def gender = user.male ? "male" : "female"  //traditional ternary operator usage

def displayName = user.name ?: "Anonymous"  //more compact Elvis operator

Operator Navigasi Aman (?)

Operator Navigasi Aman digunakan untuk menghindari NullPointerException. Biasanya ketika Anda memiliki referensi ke objek Anda mungkin perlu memverifikasi bahwa itu tidak nol sebelum mengakses metode atau properti objek. Untuk menghindari hal ini, operator navigasi yang aman hanya akan mengembalikan nol alih-alih melemparkan pengecualian, seperti:

def user = User.find( "admin" )           //this might be null if 'admin' does not exist
def streetName = user?.address?.street    //streetName will be null if user or user.address is null - no NPE thrown
tiagomac
sumber
9
'Operator Elvis' ada di C # - tapi itu disebut operator penggabungan nol (jauh lebih menarik) :-)
Cameron
Jika Anda menginginkan sintaks alternatif, Anda dapat melihat cofeescript
Lime
Pertanyaan ini agak berantakan ... mencampur 3 operator yang berbeda? : (operator ternery, dijabarkan dalam pertanyaan, mungkin salah ketik), ?? (null penggabungan, yang memang ada dalam JavaScript) dan? (Elvis) yang TIDAK ada dalam JavaScript. Jawabannya tidak menjelaskan perbedaan ini dengan sangat baik.
JoelFan
2
@ JoelFan dapatkah Anda memberikan tautan ke dokumentasi tentang null-coalescence ( ??) yang tepat dalam javascript? Semua yang saya temukan sejauh ini menunjukkan bahwa JS hanya memiliki penggabungan "falsey" (menggunakan ||).
Charles Wood
1
Yah, aku tidak bermaksud mengatakan bahwa JS benar-benar memilikinya ?? tapi itu nol-menyatu ... tapi bahkan di sana aku agak salah. Yang sedang berkata, saya telah melihat BANYAK kode JS yang menggunakan || sebagai null coalesce, meskipun jebakan
falsey

Jawaban:

139

Anda dapat menggunakan operator 'OR' yang logis sebagai pengganti operator Elvis:

Sebagai contoh displayname = user.name || "Anonymous".

Tetapi Javascript saat ini tidak memiliki fungsi lain. Saya akan merekomendasikan melihat CoffeeScript jika Anda ingin sintaks alternatif. Ini memiliki beberapa singkatan yang mirip dengan apa yang Anda cari.

Misalnya Operator yang Ada

zip = lottery.drawWinner?().address?.zipcode

Pintasan fungsi

()->  // equivalent to function(){}

Panggilan fungsi seksi

func 'arg1','arg2' // equivalent to func('arg1','arg2')

Ada juga komentar dan kelas multiline. Jelas Anda harus mengkompilasi ini ke javascript atau menyisipkan ke halaman <script type='text/coffeescript>'tetapi menambahkan banyak fungsi :). Penggunaan <script type='text/coffeescript'>sebenarnya hanya ditujukan untuk pengembangan dan bukan produksi.

jeruk nipis
sumber
14
logis atau tidak cukup hal yang diperlukan dalam kebanyakan kasus, karena Anda mungkin ingin operan yang tepat hanya jika kiri tidak terdefinisi tetapi tidak ketika itu didefinisikan dan salah.
user2451227
Apakah ini kesalahanku, atau memang benar <script type='coffee/script>'?
JCCM
2
Halaman beranda CoffeeScript menggunakan <script type="text/coffeescript">.
Elias Zamaria
19
Sementara ini menjawab pertanyaan itu hampir seluruhnya tentang coffeescript daripada javascript, dan lebih dari setengah tentang menggambarkan manfaat coffeescript yang tidak terkait dengan OP. Saya sarankan untuk meringkas apa yang relevan dengan pertanyaan, sehebat manfaat kopi lainnya.
jinglesthula
4
Apakah saya akan pisang? Tentunya keberatan dari user2451227 (saat ini dengan 4 suara) tidak valid karena operan tengah ternary (yaitu operan kanan dengan operator Elvis) sama-sama tidak akan dipilih jika ekspresi / operan kiri didefinisikan dan salah. Dalam kedua kasus Anda kemudian harus pergi x === undefined.
mike rodent
115

Saya pikir yang berikut ini setara dengan operator navigasi yang aman, meskipun sedikit lebih lama:

var streetName = user && user.address && user.address.street;

streetNamemaka akan menjadi nilai user.address.streetatau undefined.

Jika Anda ingin default ke hal lain, Anda dapat menggabungkan dengan cara pintas di atas atau memberikan:

var streetName = (user && user.address && user.address.street) || "Unknown Street";
samjudson
sumber
7
ditambah satu untuk contoh yang bagus dari propagasi nol dan koalesensi nol!
Jay Wick
1
ini berfungsi kecuali bahwa Anda tidak akan tahu apakah Anda mendapatkan nol atau tidak ditentukan
Dave Cousineau
82

Operator ATAU logis Javascript adalah hubungan arus pendek dan dapat menggantikan operator "Elvis" Anda:

var displayName = user.name || "Anonymous";

Namun, setahu saya tidak ada yang setara dengan ?.operator Anda .

Frédéric Hamidi
sumber
13
+1, saya lupa ||bisa digunakan seperti itu. Sadarilah bahwa ini akan menyatu tidak hanya ketika ekspresi adalah null, tetapi juga ketika itu terdefinisi, false, 0, atau string kosong.
Cameron
@Cameron, memang, tapi itu disebutkan dalam pertanyaan dan tampaknya niat si penanya. ""atau 0mungkin tidak terduga, :)
Frédéric Hamidi
72

Saya kadang-kadang menemukan ungkapan berikut berguna:

a?.b?.c

dapat ditulis ulang sebagai:

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

Ini mengambil keuntungan dari fakta bahwa mendapatkan atribut yang tidak dikenal pada suatu objek menghasilkan undefined, daripada melemparkan pengecualian seperti pada nullatau undefined, jadi kami mengganti null dan undefined dengan objek kosong sebelum menavigasi.

James_pic
sumber
14
Yah, sulit dibaca tetapi lebih baik dari &&metode verbose itu . +1.
pekik
1
Sebenarnya itu satu-satunya operator yang benar-benar aman dalam javascript. Operator 'OR' logis yang disebutkan di atas adalah sesuatu yang lain.
vasilakisfil
@Filippos dapatkah Anda memberikan contoh perilaku yang berbeda dalam metode logika ATAU vs &&? Saya tidak bisa memikirkan perbedaan
The Red Pea
Ini juga memungkinkan menavigasi nilai anonim tanpa menugaskannya ke variabel terlebih dahulu.
Matt Jenkins
1
Suka! Sangat berguna jika Anda ingin mendapatkan properti objek setelah operasi array.find () yang mungkin tidak memberikan hasil apa pun
Shiraz
24

Saya pikir lodash _.get()dapat membantu di sini, seperti dalam _.get(user, 'name'), dan tugas-tugas yang lebih kompleks seperti_.get(o, 'a[0].b.c', 'default-value')

tony_k
sumber
5
Masalah utama saya dengan metode ini adalah kenyataan bahwa karena nama properti adalah string, Anda tidak dapat menggunakan fungsi refactoring dari IDE Anda dengan kepercayaan 100% lagi
RPDeshaies
21

Saat ini ada spesifikasi rancangan:

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

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

Untuk saat ini, saya suka menggunakan lodashget(object, path [,defaultValue]) atau dlvdelve(obj, keypath)

Pembaruan (per 23 Desember 2019):

rantai opsional telah pindah ke tahap 4

Jack Tuck
sumber
Lodash membuat pemrograman dalam javascript lebih enak
tokek
2
rantai opsional baru-baru ini pindah ke tahap 4 , jadi kita akan melihatnya di ES2020
Nick Parsons
1
@NickParsons Terima kasih! Telah memperbarui jawabannya.
Jack Tuck
18

Pembaruan 2019

JavaScript sekarang memiliki padanan untuk Operator Elvis dan Operator Navigasi Aman.


Akses Properti Aman

The chaining Operator opsional ( ?.) saat ini menjadi tahap 4 ECMAScript usulan . Anda dapat menggunakannya hari ini dengan Babel .

// `undefined` if either `a` or `b` are `null`/`undefined`. `a.b.c` otherwise.
const myVariable = a?.b?.c;

The logis operator AND ( &&) adalah "tua", cara yang lebih-verbose untuk menangani skenario ini.

const myVariable = a && a.b && a.c;

Memberikan Default

The Operator penggabungan nullish ( ??) saat ini menjadi tahap 3 ECMAScript usulan . Anda dapat menggunakannya hari ini dengan Babel . Ini memungkinkan Anda untuk menetapkan nilai default jika sisi kiri operator adalah nilai nullary ( null/ undefined).

const myVariable = a?.b?.c ?? 'Some other value';

// Evaluates to 'Some other value'
const myVariable2 = null ?? 'Some other value';

// Evaluates to ''
const myVariable3 = '' ?? 'Some other value';

The Operator OR logis ( ||) merupakan solusi alternatif dengan perilaku yang sedikit berbeda . Hal ini memungkinkan Anda untuk mengatur nilai default jika sisi kiri dari operator adalah falsy . Perhatikan bahwa hasil di myVariable3bawah berbeda dari di myVariable3atas.

const myVariable = a?.b?.c || 'Some other value';

// Evaluates to 'Some other value'
const myVariable2 = null || 'Some other value';

// Evaluates to 'Some other value'
const myVariable3 = '' || 'Some other value';
Jabacchetta
sumber
1
Jawaban ini perlu lebih banyak upvotes. Operator Nullish Coalescing sekarang dalam tahap 4.
Yerke
13

Untuk yang pertama, bisa Anda gunakan ||. Operator "logis atau" Javascript, alih-alih hanya mengembalikan nilai kalengan benar dan salah, mengikuti aturan mengembalikan argumen kirinya jika itu benar, dan sebaliknya mengevaluasi dan mengembalikan argumen kanannya. Ketika Anda hanya tertarik pada nilai kebenaran, hasilnya sama saja, tetapi itu juga berarti foo || bar || bazmengembalikan nilai foo, bar, atau baz paling kiri yang berisi nilai sebenarnya .

Anda tidak akan menemukan satu yang dapat membedakan false dari null, dan 0 dan string kosong adalah nilai-nilai yang salah, jadi hindari menggunakan value || defaultkonstruk yang valuebisa bernilai 0 atau "".

hobbs
sumber
4
Pekerjaan bagus mencatat bahwa ini dapat menghasilkan perilaku yang tidak terduga ketika operan kiri adalah nilai falsey non-null.
Shog9
11

Ya ada! 🍾

Chaining opsional ada di tahap 4 dan ini memungkinkan Anda untuk menggunakan user?.address?.streetformula.

Jika Anda tidak bisa menunggu rilis, instal @babel/plugin-proposal-optional-chainingdan Anda dapat menggunakannya. Berikut adalah pengaturan saya yang berfungsi untuk saya, atau cukup baca artikel Nimmo .

// package.json

{
  "name": "optional-chaining-test",
  "version": "1.0.0",
  "main": "index.js",
  "devDependencies": {
    "@babel/plugin-proposal-optional-chaining": "7.2.0",
    "@babel/core": "7.2.0",
    "@babel/preset-env": "^7.5.5"
  }
  ...
}
// .babelrc

{
  "presets": [
    [
      "@babel/preset-env",
      {
        "debug": true
      }
    ]
  ],
  "plugins": [
    "@babel/plugin-proposal-optional-chaining"
  ]
}
// index.js

console.log(user?.address?.street);  // it works
gazdagergo
sumber
4
Dia bertanya apakah ada satu, bukan apakah Anda bisa menambahkannya. Saya pikir ini bukan super berguna mengingat tidak apa yang diminta.
DeanMWake
2
Itu mencapai tahap 3 dari proses standardisasi ECMAScript. es2020 🚀 - babeljs.io/docs/en/babel-plugin-proposal-optional-chaining
wedi
Saya pikir jawaban ini menyesatkan apa adanya.
Leonardo Raele
1
Jawaban ini tidak sepenuhnya benar! Chaining opsional masih dalam tahap 3 dan ES2020 belum dirilis atau bahkan belum selesai. Setidaknya Anda menyebutkan bagaimana seseorang dapat menggunakannya tanpa harus menunggu untuk dirilis.
Maxie Berkmann
@gazdagergo Tidak masalah :).
Maxie Berkmann
6

Berikut ini setara dengan operator elvis:

function elvis(object, path) {
    return path ? path.split('.').reduce(function (nestedObject, key) {
        return nestedObject && nestedObject[key];
    }, object) : object;
}

> var o = { a: { b: 2 }, c: 3 };
> elvis(o)

{ a: { b: 2 }, c: 3 }

> elvis(o, 'a');

{ b: 2 }

> elvis(o, 'a.b');

2

> elvis(o, 'x');

undefined
cdmckay
sumber
5

UPDATE SEP 2019

Ya, JS sekarang mendukung ini. Chaining opsional akan segera hadir untuk v8 baca selengkapnya

Taher
sumber
Tidak persis sama. OP aktif tentang null penggabungan, tapi tetap saja jawaban yang bagus.
Maxie Berkmann
4

Ini lebih dikenal sebagai operator penggabungan nol. Javascript tidak memilikinya.

CassOnMars
sumber
3
benar dalam arti yang ketat, tetapi sebagai jawaban lain telah mencatat operator logika ATAU JavaScript dapat berperilaku sebagai operator penggabungan palsu , yang memungkinkan Anda untuk mencapai keringkasan yang sama dalam banyak situasi.
Shog9
1
Ini bukan operator penggabungan nol. Null-penggabungan hanya bekerja pada nilai tunggal, bukan pada rantai akses properti / pemanggilan fungsi. Anda sudah dapat melakukan penggabungan nol dengan operator logika ATAU dalam JavaScript.
Tidak, Anda dapat melakukan penggabungan salah dengan logika ATAU dalam JavaScript.
andresp
3

Anda dapat mencapai efek yang hampir sama dengan mengatakan:

var displayName = user.name || "Anonymous";
Justin Ethier
sumber
2

Saya punya solusi untuk itu, sesuaikan dengan kebutuhan Anda sendiri, kutipan dari salah satu lib saya:

    elvisStructureSeparator: '.',

    // An Elvis operator replacement. See:
    // http://coffeescript.org/ --> The Existential Operator
    // http://fantom.org/doc/docLang/Expressions.html#safeInvoke
    //
    // The fn parameter has a SPECIAL SYNTAX. E.g.
    // some.structure['with a selector like this'].value transforms to
    // 'some.structure.with a selector like this.value' as an fn parameter.
    //
    // Configurable with tulebox.elvisStructureSeparator.
    //
    // Usage examples: 
    // tulebox.elvis(scope, 'arbitrary.path.to.a.function', fnParamA, fnParamB, fnParamC);
    // tulebox.elvis(this, 'currentNode.favicon.filename');
    elvis: function (scope, fn) {
        tulebox.dbg('tulebox.elvis(' + scope + ', ' + fn + ', args...)');

        var implicitMsg = '....implicit value: undefined ';

        if (arguments.length < 2) {
            tulebox.dbg(implicitMsg + '(1)');
            return undefined;
        }

        // prepare args
        var args = [].slice.call(arguments, 2);
        if (scope === null || fn === null || scope === undefined || fn === undefined 
            || typeof fn !== 'string') {
            tulebox.dbg(implicitMsg + '(2)');
            return undefined;   
        }

        // check levels
        var levels = fn.split(tulebox.elvisStructureSeparator);
        if (levels.length < 1) {
            tulebox.dbg(implicitMsg + '(3)');
            return undefined;
        }

        var lastLevel = scope;

        for (var i = 0; i < levels.length; i++) {
            if (lastLevel[levels[i]] === undefined) {
                tulebox.dbg(implicitMsg + '(4)');
                return undefined;
            }
            lastLevel = lastLevel[levels[i]];
        }

        // real return value
        if (typeof lastLevel === 'function') {
            var ret = lastLevel.apply(scope, args);
            tulebox.dbg('....function value: ' + ret);
            return ret;
        } else {
            tulebox.dbg('....direct value: ' + lastLevel);
            return lastLevel;
        }
    },

bekerja seperti pesona. Nikmati sedikit rasa sakit!

Balazstth
sumber
Terlihat menjanjikan, dapatkah Anda mengirimkan sumber lengkap? apakah Anda memilikinya di tempat umum? (eg GitHub)
Eran Medan
1
Saya akan membuat kutipan kecil dari kode yang saya gunakan dan akan mempostingnya di GitHub dalam seminggu atau lebih.
balazstth
2

Anda bisa menggulung sendiri:

function resolve(objectToGetValueFrom, stringOfDotSeparatedParameters) {
    var returnObject = objectToGetValueFrom,
        parameters = stringOfDotSeparatedParameters.split('.'),
        i,
        parameter;

    for (i = 0; i < parameters.length; i++) {
        parameter = parameters[i];

        returnObject = returnObject[parameter];

        if (returnObject === undefined) {
            break;
        }
    }
    return returnObject;
};

Dan gunakan seperti ini:

var result = resolve(obj, 'a.b.c.d'); 

* hasil tidak ditentukan jika salah satu dari a, b, c atau d tidak terdefinisi.

Pylinux
sumber
1

Saya membaca artikel ini ( https://www.beyondjava.net/elvis-operator-aka-safe-navigation-javascript-typescript ) dan memodifikasi solusi menggunakan Proxies.

function safe(obj) {
    return new Proxy(obj, {
        get: function(target, name) {
            const result = target[name];
            if (!!result) {
                return (result instanceof Object)? safe(result) : result;
            }
            return safe.nullObj;
        },
    });
}

safe.nullObj = safe({});
safe.safeGet= function(obj, expression) {
    let safeObj = safe(obj);
    let safeResult = expression(safeObj);

    if (safeResult === safe.nullObj) {
        return undefined;
    }
    return safeResult;
}

Anda menyebutnya seperti ini:

safe.safeGet(example, (x) => x.foo.woo)

Hasilnya akan tidak ditentukan untuk ekspresi yang menemukan nol atau tidak terdefinisi di sepanjang jalurnya. Anda bisa menjadi liar dan memodifikasi prototipe Obyek!

Object.prototype.getSafe = function (expression) {
    return safe.safeGet(this, expression);
};

example.getSafe((x) => x.foo.woo);
Sam
sumber
1

Ini masalah bagi saya untuk waktu yang lama. Saya harus datang dengan solusi yang dapat dengan mudah dimigrasi begitu kita mendapatkan operator Elvis atau sesuatu.

Inilah yang saya gunakan; bekerja untuk array dan objek

letakkan ini di file tools.js atau apalah

// this will create the object/array if null
Object.prototype.__ = function (prop) {
    if (this[prop] === undefined)
        this[prop] = typeof prop == 'number' ? [] : {}
    return this[prop]
};

// this will just check if object/array is null
Object.prototype._ = function (prop) {
    return this[prop] === undefined ? {} : this[prop]
};

contoh penggunaan:

let student = {
    classes: [
        'math',
        'whatev'
    ],
    scores: {
        math: 9,
        whatev: 20
    },
    loans: [
        200,
        { 'hey': 'sup' },
        500,
        300,
        8000,
        3000000
    ]
}

// use one underscore to test

console.log(student._('classes')._(0)) // math
console.log(student._('classes')._(3)) // {}
console.log(student._('sports')._(3)._('injuries')) // {}
console.log(student._('scores')._('whatev')) // 20
console.log(student._('blabla')._('whatev')) // {}
console.log(student._('loans')._(2)) // 500 
console.log(student._('loans')._(1)._('hey')) // sup
console.log(student._('loans')._(6)._('hey')) // {} 

// use two underscores to create if null

student.__('loans').__(6)['test'] = 'whatev'

console.log(student.__('loans').__(6).__('test')) // whatev

Yah aku tahu itu membuat kode sedikit tidak terbaca tetapi itu adalah solusi satu liner sederhana dan berfungsi dengan baik. Saya harap ini membantu seseorang :)

Netral
sumber
0

Ini adalah solusi menarik untuk operator navigasi yang aman menggunakan beberapa campuran ..

http://jsfiddle.net/avernet/npcmv/

  // Assume you have the following data structure
  var companies = {
      orbeon: {
          cfo: "Erik",
          cto: "Alex"
      }
  };

  // Extend Underscore.js
  _.mixin({ 
      // Safe navigation
      attr: function(obj, name) { return obj == null ? obj : obj[name]; },
      // So we can chain console.log
      log: function(obj) { console.log(obj); }
  });

  // Shortcut, 'cause I'm lazy
  var C = _(companies).chain();

  // Simple case: returns Erik
  C.attr("orbeon").attr("cfo").log();
  // Simple case too, no CEO in Orbeon, returns undefined
  C.attr("orbeon").attr("ceo").log();
  // IBM unknown, but doesn't lead to an error, returns undefined
  C.attr("ibm").attr("ceo").log();
Dean Hiller
sumber
0

Saya membuat paket yang membuat ini jauh lebih mudah digunakan.

NPM jsdig Github jsdig

Anda dapat menangani hal-hal sederhana seperti dan objek:

const world = {
  locations: {
    europe: 'Munich',
    usa: 'Indianapolis'
  }
};

world.dig('locations', 'usa');
// => 'Indianapolis'

world.dig('locations', 'asia', 'japan');
// => 'null'

atau sedikit lebih rumit:

const germany = () => 'germany';
const world = [0, 1, { location: { europe: germany } }, 3];
world.dig(2, 'location', 'europe') === germany;
world.dig(2, 'location', 'europe')() === 'germany';
Devchris
sumber
-6

Secara pribadi saya menggunakan

function e(e,expr){try{return eval(expr);}catch(e){return null;}};

dan misalnya aman dapatkan:

var a = e(obj,'e.x.y.z.searchedField');
kudłaty
sumber
2
Pertama-tama Anda sebaiknya tidak menggunakan eval . Kedua ini bahkan tidak berhasil: e({a:{b:{c:{d:'test'}}}}, 'a.b.c.d')pengembalian null.
Pylinux
@Pylinux pada dasarnya apa yang akan bekerja adalah e = eval, var a = eval('obj.a.b.c.d'). evalbahkan tidak mengambil parameter kedua ... developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/…
Dorian