Javascript'te boş birleştirme (Elvis) operatörü veya güvenli navigasyon operatörü var mı?


210

Örnek olarak açıklayacağım:

Elvis Operatörü (?:)

"Elvis operatörü", Java'nın üçlü operatörünün kısalmasıdır. Bunun kullanışlı olduğu durumlardan biri, bir ifade yanlış veya null değerine çözümlenirse 'makul varsayılan' değer döndürmektir. Basit bir örnek şöyle görünebilir:

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

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

Güvenli Navigasyon Operatörü (?.)

Güvenli Gezinme işleci, NullPointerException özel durumundan kaçınmak için kullanılır. Genellikle bir nesneye başvurunuz olduğunda, nesnenin yöntemlerine veya özelliklerine erişmeden önce nesnenin boş olmadığını doğrulamanız gerekebilir. Bundan kaçınmak için, güvenli navigasyon operatörü aşağıdaki gibi bir istisna atmak yerine null değerini döndürür:

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

9
'Elvis Operatörü' C # 'da var ama buna boş birleştirici operatör denir (çok daha az heyecan verici) :-)
Cameron

Alternatif bir sözdizimi istiyorsanız cofeescript
Lime

Bu soru biraz karışıklık ... 3 farklı operatörü karıştırıyor mu? : (üçlü operatör, muhtemelen bir yazım hatası), ??? (JavaScript'te bulunan boş birleştirme) ve?. (Elvis) olan JavaScript'te mevcut DEĞİLDİR. Cevaplar bu ayrımı pek açıklamıyor.
JoelFan

2
@JoelFavascript'te uygun null birleşme ( ??) ile ilgili belgelere bir bağlantı sağlayabilir misiniz ? Şimdiye kadar bulduğum her şey JS'nin sadece "falsey" birleştirici (kullanarak ||) olduğunu gösteriyor.
Charles Wood

1
Peki, JS'nin tam anlamıyla olduğunu söylemek istemedim ?? ama boş bir birleşim olduğunu ... ama orada bile biraz yanılmışım. Olduğu söyleniyor, ben kullanan JS kodu bir sürü gördüm ||
falsey

Yanıtlar:


139

Elvis operatörü yerine mantıksal 'VEYA' operatörünü kullanabilirsiniz:

Örneğin displayname = user.name || "Anonymous" .

Ancak Javascript şu anda başka bir işleve sahip değil. Alternatif bir sözdizimi istiyorsanız CoffeeScript'e bakmanızı tavsiye ederim . Aradığınıza benzer bir stenografi var.

Örneğin Varoluşçu İşleç

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

İşlev kısayolları

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

Seksi fonksiyon arama

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

Çok satırlı yorumlar ve sınıflar da vardır. Açıkçası bu javascript derlemek veya sayfaya eklemek <script type='text/coffeescript>'zorunda ama çok işlevsellik ekler :). Kullanmak <script type='text/coffeescript'>gerçekten sadece üretime yöneliktir, üretime yönelik değildir.


14
mantıksal veya çoğu durumda tam olarak gerekli olan şey değildir, çünkü sadece sol tanımsızsa tanımlanmış ve yanlış olduğunda değil, sağ işleneni seçmesini isteyebilirsiniz.
user2451227

Benim hatam mı, yoksa gerçekten <script type='coffee/script>'mi?
JCCM

2
CoffeeScript ana sayfası kullanır <script type="text/coffeescript">.
Elias Zamaria

19
Bu soruyu cevaplasa da, neredeyse tamamen javascript yerine kahve ile ilgilidir ve OP ile alakasız kahve faydalarının tanımlanmasının yarısından fazlasıdır. Bunu, kahvenin diğer faydaları kadar harika, soruyla alakalı olana kadar kaynatmanızı öneririm.
jinglesthula

4
Muz muyum? Şüphesiz user2451227'nin (şu anda 4 oyla) itirazı geçersizdir, çünkü ifadenin / sol işlenenin tanımlanması ve yanlış olması durumunda üçgenin orta işleneni (yani Elvis operatörü ile sağ işlenen) eşit olarak seçilmez. Her iki durumda da gitmeniz gerekir x === undefined.
mike kemirgen

115

Bence biraz daha uzun olsa da, aşağıdaki güvenli navigasyon operatörüne eşdeğer:

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

streetNameDaha sonra değeri ya olacak user.address.streetya da undefined.

Varsayılan olarak başka bir şeye ayarlanmasını istiyorsanız, yukarıdaki kısayolla birleştirebilir veya şunları verebilirsiniz:

var streetName = (user && user.address && user.address.street) || "Unknown Street";

7
artı bir null yayılma ve null birleşme harika bir örnek!
Jay Wick

1
bu işe
yaramazsa

82

JavaScript'ın mantıksal OR operatörü olan kısa devre ve "Elvis" operatörünü değiştirebilirsiniz:

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

Ancak bildiklerime göre ?.operatörünüzle eşdeğer değil .


13
+1, ||bu şekilde kullanılabileceğini unuttum . Bunun sadece ifade olduğunda birleşmeyeceğini unutmayın.null , aynı zamanda tanımsız oluyor, false, 0, veya boş dize.
Cameron

@Cameron, aslında, ama bu soruda bahsedildi ve sorucının niyeti gibi görünüyor. ""ya 0da beklenmedik olabilir :)
Frédéric Hamidi

72

Bazen aşağıdaki deyimi yararlı buldum:

a?.b?.c

şu şekilde yeniden yazılabilir:

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

Bu, bir nesne üzerinde bilinmeyen özniteliklerin elde edilmesinin, üzerinde olduğu gibi bir istisna atmak yerine tanımsız döndüğü gerçeğinden yararlanır nullveya undefinedbu nedenle, gezinmeden önce null ve undefined öğelerini boş bir nesneyle değiştiririz.


14
Okumak zor ama bu ayrıntılı &&yöntemden daha iyi . +1.
shriek

1
Bu aslında javascriptteki tek gerçek güvenli operatör. Yukarıda belirtilen mantıksal 'VEYA' operatörü başka bir şeydir.
vasilakisfil

@Filippos mantıksal OR vs && yönteminde farklı davranış örnekleri verebilir misiniz? Bir fark düşünemiyorum
The Red Pea

Ayrıca, önce bir değişkene atamadan anonim bir değerde gezinmeyi de sağlar.
Matt Jenkins

1
Sevdim! Herhangi bir sonuç döndürmeyebilecek bir array.find () işleminden sonra bir nesnenin özelliğini almak istiyorsanız gerçekten kullanışlıdır
Shiraz

24

bence lodash _.get()burada olduğu gibi _.get(user, 'name')ve daha karmaşık görevler gibi_.get(o, 'a[0].b.c', 'default-value')


5
Bu yöntemle ilgili ana sorunum, özelliklerin adı dize olduğundan,
IDE'nizin

21

Şu anda bir taslak spesifikasyon var:

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

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

Şimdilik olsa da, lodashget(object, path [,defaultValue]) veya dlv kullanmayı seviyorumdelve(obj, keypath)

Güncelleme (23 Aralık 2019 itibariyle):

isteğe bağlı zincirleme aşama 4'e taşındı


Javascript Lodash yapmak programlama daha lezzetli
kertenkeleleri

2
isteğe bağlı zincirleme kısa bir süre önce 4. aşamaya taşındı , bu yüzden ES2020'de göreceğiz
Nick Parsons

1
@NickParsons Teşekkürler! Cevabı güncellediniz.
Jack Tuck

18

2019 Güncellemesi

JavaScript artık Elvis Operatörü ve Güvenli Navigasyon Operatörü için eşdeğerlere sahip.


Güvenli Mülk Erişimi

İsteğe bağlı zincirleme operatörü ( ?.) henüz olan evre 4 ECMAScript önerisi . Şunları yapabilirsiniz Babel ile bugün kullanabilirsiniz .

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

Mantıksal AND operatörü ( &&) Bu senaryoyu ele "eski", daha ayrintili bir yoldur.

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

Varsayılan Verme

Nullish birleştirici operatörü ( ??) henüz olan evre 3 ECMAScript önerisi . Şunları yapabilirsiniz Babel ile bugün kullanabilirsiniz . Operatörün sol tarafı boş bir değer ( null/ undefined) ise varsayılan bir değer ayarlamanızı sağlar .

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';

Mantıksal VEYA operatörü ( ||) alternatif bir çözüm biraz farklı bir davranış ile . Operatörün sol tarafı hatalıysa varsayılan bir değer ayarlamanızı sağlar . myVariable3Aşağıdakilerin sonucunun myVariable3yukarıdakilerden farklı olduğunu unutmayın .

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';

1
Bu cevap için daha fazla oy gerekiyor. Nullish Coalescing Operator şimdi 4. aşamada.
Yerke

13

Birincisi için kullanabilirsiniz ||. Javascript "mantıksal veya" operatörü, sadece konserve doğru ve yanlış değerleri döndürmek yerine, doğruysa sol argümanını döndürme, aksi takdirde sağ argümanını değerlendirme ve döndürme kuralını uygular. Yalnızca doğruluk değeriyle ilgileniyorsanız, aynı şekilde çalışır, ancak aynı zamanda gerçek bir değer içeren foo, bar veya baz'ın en soldaki değerini foo || bar || bazdöndürür . .

False değerini null değerinden ayırabilecek bir tane bulamazsınız ve 0 ve boş dize yanlış değerlerdir, bu nedenle meşru bir şekilde 0 veya olabileceği bir value || defaultyapı kullanmaktan kaçının .value""


4
Sol işlenen boş olmayan bir falsey değeri olduğunda bunun beklenmedik davranışlarla sonuçlanabileceğini belirten iyi bir iş.
Shog9

11

Evet var! 🍾

İsteğe bağlı zincirleme 4. aşamadadır ve bu user?.address?.streetformülü kullanmanızı sağlar .

Sürümü bekleyemiyorsanız, yükleyin @babel/plugin-proposal-optional-chainingve kullanabilirsiniz. İşte benim için çalışan ayarlarım veya sadece Nimmo'nun makalesini okuyun .

// 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

4
Bir tane ekleyip ekleyemeyeceğinizi değil, bir tane olup olmadığını sordu. Bu isnt süper yararlı olduğunu düşünüyor onun değil ne soruldu.
DeanMWake

2
ECMAScript standardizasyon sürecinin 3. aşamasına ulaştı. es2020 🚀 - babeljs.io/docs/en/babel-plugin-proposal-optional-chaining
wedi

Bence bu cevap olduğu gibi yanıltıcı.
Leonardo Raele

1
Bu cevap doğru değil! İsteğe bağlı zincirleme hala aşama 3'te ve ES2020 henüz piyasaya sürülmedi ve hatta sonlandırılmadı. En azından serbest bırakılmasını beklemek zorunda kalmadan nasıl kullanabileceğinizden bahsettiniz.
Maxie Berkmann

@gazdagergo Sorun değil :).
Maxie Berkmann

6

İşte basit bir elvis operatör eşdeğeri:

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

5

GÜNCELLEME EYLÜL 2019

Evet, JS artık bunu destekliyor. V8 İhtiyari zincirleme yakında devamını oku


Tam olarak aynı değil. OP boş değer birleştirmeyle ilgili, ancak yine de güzel bir cevap.
Maxie Berkmann

4

Bu daha çok boş birleştirme operatörü olarak bilinir. Javascript yok.


3
katı anlamda doğrudur, ancak diğer cevapların belirttiği gibi, JavaScript'in mantıksal OR operatörü bir tür yanlış birleşme operatörü olarak davranabilir ve birçok durumda aynı kısalığı elde etmenizi sağlar.
Shog9

1
Bu null birleştirme operatörü değildir. Null birleştirme özelliği erişim / işlev çağırma zincirinde değil, yalnızca tek bir değer üzerinde çalışır. JavaScript'te mantıksal OR operatörü ile zaten boş birleştirme yapabilirsiniz.

Hayır, JavaScript'te mantıksal OR ile yanlış birleştirme yapabilirsiniz.
andresp

3

Aşağıdakileri söyleyerek kabaca aynı etkiyi elde edebilirsiniz:

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

2

Bunun için bir çözümüm var, kendi ihtiyaçlarınıza göre uyarlıyorum, dudaklarımdan bir alıntı:

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

tıkır tıkır çalışıyor. Daha az acıyı yaşayın!


Umut verici görünüyor, lütfen kaynağın tamamını gönderebilir misiniz? herkese açık bir yerde var mı? (örn. GitHub)
Eran Medan

1
İçinde kullandığım koddan küçük bir alıntı oluşturacağım ve bir hafta kadar sonra GitHub'a göndereceğim.
balazstth

2

Kendinizi yuvarlayabilirsiniz:

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

Ve şu şekilde kullanın:

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

*, a, b, c veya d öğelerinden herhangi biri tanımsızsa sonuç tanımsızdır.


1

Bu makaleyi okudum ( https://www.beyondjava.net/elvis-operator-aka-safe-navigation-javascript-typescript ) ve Proxy'leri kullanarak çözümü değiştirdim.

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

Buna şöyle diyorsunuz:

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

Sonuç, yolu boyunca null veya undefined ile karşılaşan bir ifade için tanımsız olacaktır. Sen gidebiliriz vahşi ve Nesne prototip değiştirmek!

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

example.getSafe((x) => x.foo.woo);


1

Bu uzun zamandır benim için bir sorundu. Elvis operatörü falan aldığımızda kolayca taşınabilecek bir çözüm bulmalıydım.

Ben bunu kullanıyorum; hem diziler hem de nesneler için çalışır

Bunu tools.js dosyasına falan koy

// 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]
};

kullanım örneği:

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

iyi kod biraz okunamaz yapar biliyorum ama basit bir astar çözüm ve harika çalışıyor. Umarım birine yardımcı olur :)


0

Bu, bazı mixin kullanan güvenli navigasyon operatörü için ilginç bir çözümdü.

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

0

Bunun kullanımını daha kolay hale getiren bir paket oluşturdum.

NPM jsdig Github jsdig

Nesne gibi basit şeyleri işleyebilirsiniz:

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

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

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

veya biraz daha karmaşık:

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

-6

Şahsen kullanıyorum

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

ve örneğin güvenli get:

var a = e(obj,'e.x.y.z.searchedField');

2
İlk olarak gerçekten eval kullanmamalısınız . İkincisi, bu bile çalışmaz: e({a:{b:{c:{d:'test'}}}}, 'a.b.c.d')döner null.
Pylinux

@Pylinux temelde çalışacak budur e = eval, var a = eval('obj.a.b.c.d'). evalikinci bir parametre bile almıyor ... developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/…
Dorian
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.