자바 스크립트에 null-coalescing (Elvis) 연산자 또는 안전한 탐색 연산자가 있습니까?


209

예를 들어 설명하겠습니다.

엘비스 연산자 (? :)

"Elvis 연산자"는 Java의 3 진 연산자를 줄입니다. 이것이 편리한 곳의 한 예는 표현식이 false 또는 null로 해석되는 경우 '감지 가능한 기본값'을 반환하는 것입니다. 간단한 예는 다음과 같습니다.

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

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

안전 탐색 연산자 (?.)

안전 탐색 연산자는 NullPointerException을 피하기 위해 사용됩니다. 일반적으로 객체에 대한 참조가있는 경우 객체의 메서드 나 속성에 액세스하기 전에 객체가 null이 아닌지 확인해야합니다. 이를 피하기 위해 안전한 탐색 연산자는 다음과 같이 예외를 발생시키는 대신 단순히 null을 반환합니다.

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 Operator'는 C #에 존재하지만 null 병합 연산자 (훨씬 덜 흥미 롭습니다)라고합니다
Cameron

다른 구문을 원한다면 cofeescript를 살펴볼 수 있습니다
Lime

이 질문은 일종의 혼란입니다 ... 3 명의 다른 연산자를 섞고 있습니까? : (문의에서 철자 한 오타 연산자, 오타 일 가능성이 있음), ?? (JavaScript에 존재하는 null 병합) 및?. JavaScript에는 존재하지 않는 (Elvis) 그 대답은이 구별을 잘 설명하지 못한다.
JoelFan

2
@JoelFan ??자바 스크립트에서 적절한 null-coalescence ( )에 관한 문서에 대한 링크를 제공 할 수 있습니까? 지금까지 내가 찾은 모든 것은 JS에 "falsey"병합 (을 사용 ||) 만 있음을 제안합니다 .
Charles Wood

1
글쎄, 나는 JS가 문자 그대로 가지고 있다고 말하지는 않았다. 그러나 그것은 널 코레 세를 가지고 있었지만 ... 심지어 거기에서도 나는 틀렸다. 즉, ||를 사용하는 많은 JS 코드를 보았습니다. 잘못된 함정에도 불구하고 널 합체로
JoelFan

답변:


138

Elvis 연산자 대신 논리 'OR'연산자를 사용할 수 있습니다.

예를 들면 displayname = user.name || "Anonymous".

그러나 Javascript에는 현재 다른 기능이 없습니다. 다른 구문을 원하면 CoffeeScript를 보는 것이 좋습니다 . 찾고있는 것과 비슷한 약기가 있습니다.

예를 들어 존재 연산자

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

기능 단축키

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

섹시한 함수 호출

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

여러 줄 주석과 클래스도 있습니다. 분명히 이것을 자바 스크립트로 컴파일하거나 페이지에 삽입해야 <script type='text/coffeescript>'하지만 많은 기능을 추가합니다 :). 사용 <script type='text/coffeescript'>은 실제로 개발 용이 아니며 프로덕션 용이 아닙니다.


14
논리가 아니거나 대부분의 경우 필요한 것이 아닙니다. 왼쪽이 정의되지 않았지만 정의되고 허위가 아닌 경우에만 오른쪽 피연산자를 선택하기를 원할 수 있습니다.
user2451227

내 실수인가, 아니면 진짜 <script type='coffee/script>'인가?
JCCM

2
CoffeeScript 홈 페이지는을 사용합니다 <script type="text/coffeescript">.
Elias Zamaria

19
이 질문에 대한 답은 거의 전적으로 javascript가 아닌 coffeescript에 관한 것이며 OP와 관련이없는 coffeescript 이점을 설명하는 것에 대해서는 절반 이상입니다. 나는 coffeescript의 다른 이점과 마찬가지로 훌륭하게 질문과 관련이있는 것으로 끓일 것을 제안합니다.
jinglesthula

4
바나나가요? 식 / 왼쪽 피연산자가 정의되고 허위 인 경우 삼항의 중간 피연산자 (즉, Elvis 연산자를 사용한 오른쪽 피연산자)가 동일하게 선택되지 않으므로 user2451227 (현재 4 표)에 대한 이의 제기는 유효하지 않습니다. 두 경우 모두 가야 x === undefined합니다.
마이크 설치류

114

조금 더 길지만 다음은 안전한 탐색 연산자와 동일하다고 생각합니다.

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

streetName그러면 user.address.street또는 의 값이 undefined됩니다.

다른 것을 기본값으로 사용하려면 위의 단축키와 결합하거나 다음을 제공 할 수 있습니다.

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

7
null 전파와 null 병합의 훌륭한 예를 하나 더합니다!
Jay Wick 님이

1
널이 없거나 정의되지 않은 경우 알 수 없다는 점을 제외하고는 작동합니다
Dave Cousineau

82

Javascript의 논리 OR 연산자단락 되어 "Elvis"연산자를 대체 할 수 있습니다.

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

그러나 내 지식으로는 ?.운영자 와 동등한 것은 없습니다 .


13
+1, 나는 ||그런 식으로 사용될 수 있다는 것을 잊었다 . 식을 때이뿐만 아니라 유착된다는 점에 유의 null뿐만 아니라,이 정의되지 않은 것, false, 0, 또는 빈 문자열입니다.
Cameron

@Cameron은 실제로 질문에 언급되었으며 질문자의 의도 인 것 같습니다. ""또는 0예기치 않은 수 있습니다 :)하지만
프레데릭 Hamidi

72

때때로 다음 관용구가 유용하다는 것을 알았습니다.

a?.b?.c

다음과 같이 다시 작성할 수 있습니다.

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

이것은 객체에서 알 수없는 속성을 얻는 것이 null또는 on 에서처럼 예외를 던지기보다는 정의되지 않은 것을 반환한다는 사실을 이용 undefined하므로 탐색하기 전에 null 및 undefined를 빈 객체로 바꿉니다.


14
글쎄, 읽기는 어렵지만 자세한 &&방법 보다 낫습니다 . +1.
shriek

1
그것은 실제로 자바 스크립트에서 유일한 안전한 연산자입니다. 위에서 언급 한 논리적 'OR'연산자는 다른 것입니다.
vasilakisfil

@Filippos 당신은 논리 OR 대 && 방법에서 다른 행동의 예를 줄 수 있습니까? 나는 차이를 생각할 수 없다
The Red Pea

또한 변수에 먼저 할당하지 않고 익명 값을 탐색 할 수 있습니다.
매트 젠킨스

1
그것을 사랑하십시오! 어떤 결과도 반환하지 않을 수있는 array.find () 작업 후 객체의 속성을 얻으려는 경우 매우 유용합니다.
Shiraz

24

lodash _.get()가에서와 같이 여기에서 도울 수 있다고 생각 _.get(user, 'name')합니다._.get(o, 'a[0].b.c', 'default-value')


5
이 방법의 주요 문제점은 속성 이름이 문자열이므로 100 % 신뢰로 IDE의 리팩토링 기능을 더 이상 사용할 수 없다는 것입니다.
RPDeshaies

21

현재 초안 스펙이 있습니다.

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

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

그러나 지금은 lodashget(object, path [,defaultValue]) 또는 dlv 를 사용하고 싶습니다.delve(obj, keypath)

업데이트 (2019 년 12 월 23 일 현재) :

옵션 체인이 4 단계로 이동했습니다.


자바 스크립트 Lodash 메이크업 프로그래밍 더 맛
도마뱀

2
옵션 체인은 최근에 4 단계 로 옮겨 졌으므로 ES2020에서 볼 수 있습니다
Nick Parsons

1
@NickParsons 감사합니다! 답변을 업데이트했습니다.
잭 턱

18

2019 년 업데이트

JavaScript는 이제 Elvis Operator 및 Safe Navigation Operator에 해당합니다.


안전한 자산 접근

옵션 체인 연산자 ( ?.) 현재이다 단계 4 인 ECMAScript의 제안 . 오늘 Babel과 함께 사용할 수 있습니다 .

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

논리 AND 연산자 ( &&)이 시나리오를 처리 할 수있는 "오래된"더-자세한 방법입니다.

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

기본 제공

nullish 병합 연산자 ( ??) 현재 인 3 단 대한 ECMAScript 제안 . 오늘 Babel과 함께 사용할 수 있습니다 . 연산자의 왼쪽이 널값 ( 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';

논리 OR 연산자 ( ||) 다른 해결책이 약간 다른 행동 . 연산자의 왼쪽이 거짓 인 경우 기본값을 설정할 수 있습니다 . myVariable3아래 결과는 myVariable3위와 다릅니다 .

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
이 답변에는 더 많은 투표가 필요합니다. Nullish Coalescing Operator는 현재 4 단계에 있습니다.
Yerke

13

전자의 경우을 사용할 수 있습니다 ||. 단순히 미리 준비된 true 및 false 값을 반환하는 것이 아니라 Javascript "논리적"연산자는 왼쪽 인수가 true이면 반환하고 그렇지 않으면 오른쪽 인수를 평가하고 반환하는 규칙을 따릅니다. 진실 가치에만 관심이 있다면 그것은 똑같이 작동하지만 또한 진정한 가치를 포함하는 foo, bar 또는 baz의 가장 왼쪽foo || bar || baz반환 한다는 것을 의미 합니다 .

그러나 false와 null을 구별 할 수있는 것을 찾지 못하고 0과 빈 문자열은 false 값이므로 합법적으로 0 또는 일 수 있는 value || default구문을 사용하지 마십시오 .value""


4
왼쪽 피연산자가 널이 아닌 거짓 값일 때 예기치 않은 동작이 발생할 수 있다는 점에 유의하십시오.
Shog9

11

예, 있습니다! 🍾

선택적 연결 은 4 단계에 있으며 user?.address?.street수식 을 사용할 수 있습니다 .

릴리스를 기다릴 수없는 경우 설치 @babel/plugin-proposal-optional-chaining하여 사용할 수 있습니다. 여기 나에게 맞는 설정이 있거나 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

4
그는 당신이 하나를 추가 할 수 있는지 여부가 아니라 하나가 있는지 물었습니다. 나는 이것이 요청되지 않은 것을 고려할 때 유용하지 않다고 생각합니다.
DeanMWake 2009 년

2
ECMAScript 표준화 프로세스의 3 단계에 도달했습니다. es2020 🚀- babeljs.io
wedi

나는이 대답이있는 것처럼 오도 된 것으로 생각합니다.
Leonardo Raele

1
이 답변은 정확하지 않습니다! 옵션 체인 은 여전히 ​​3 단계에 있으며 ES2020은 아직 출시되지 않았거나 아직 마무리되지 않았습니다. 적어도 당신은 그것을 공개 할 때까지 기다리지 않고 어떻게 사용할 수 있는지 언급했습니다.
Maxie Berkmann

@gazdagergo 문제 없습니다 :).
Maxie Berkmann

6

다음은 간단한 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

5

2019 년 9 월 업데이트

예, JS는 이제 이것을 지원합니다. V8가 옵션 체인은 곧 제공 될 예정입니다 자세히보기


동일하지 않습니다. OP는 null 병합에 관한 것이지만 그럼에도 불구하고 좋은 대답입니다.
Maxie Berkmann

4

이것을보다 일반적으로 널 병합 연산자라고합니다. 자바 스크립트가 없습니다.


3
엄밀한 의미에서는 사실이지만 다른 답변에서 언급했듯이 JavaScript의 논리 OR 연산자는 일종의 거짓 통합 연산자 처럼 작동 하여 여러 상황에서 동일한 간결성을 얻을 수 있습니다.
Shog9

1
이것은 널 병합 연산자가 아닙니다. 널 병합은 일련의 속성 액세스 / 함수 호출이 아닌 단일 값에서만 작동합니다. JavaScript에서 논리 OR 연산자를 사용하여 이미 널 병합을 수행 할 수 있습니다.

아니요, JavaScript에서 논리 OR을 사용하여 잘못된 병합을 수행 할 수 있습니다.
andresp

3

다음과 같이 말하면 대략 동일한 효과를 얻을 수 있습니다.

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

2

나는 그것을위한 해결책을 가지고 있으며, 내 라이브러리 중 하나에서 발췌 한 자신의 필요에 맞게 조정하십시오.

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

매력처럼 작동합니다. 덜 고통을 즐기십시오!


유망한 것처럼 보이면, 전체 소스를 제출하십시오. 어디서나 공개 할 수 있습니까? (예 : GitHub)
Eran Medan

1
내가 사용하는 코드에서 작은 발췌를 만들어서 일주일 정도 GitHub에 게시 할 것입니다.
balazstth

2

당신은 당신의 자신을 굴릴 수 있습니다 :

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

그리고 이것을 다음과 같이 사용하십시오 :

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

* a, b, c 또는 d 중 하나가 정의되지 않은 경우 결과는 정의되지 않습니다.


1

이 기사 ( https://www.beyondjava.net/elvis-operator-aka-safe-navigation-javascript-typescript )를 읽고 프록시를 사용하여 솔루션을 수정했습니다.

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

당신은 이것을 다음과 같이 부릅니다.

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

경로를 따라 null 또는 undefined가있는 식의 결과는 정의되지 않습니다. 난폭하게 진행 하여 Object 프로토 타입을 수정할 수 있습니다 !

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

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


1

이것은 오랫동안 저에게 문제였습니다. 일단 Elvis 운영자 나 무언가를 얻었을 때 쉽게 마이그레이션 할 수있는 솔루션을 생각해 내야했습니다.

이것이 내가 사용하는 것입니다. 배열과 객체 모두에서 작동

tools.js 파일 또는 무언가에 이것을 넣으십시오.

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

사용 예 :

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

글쎄, 코드를 읽을 수는 없지만 간단한 단일 라이너 솔루션이며 훌륭하게 작동한다는 것을 알고 있습니다. 나는 그것이 누군가를 돕기를 바랍니다 :)


0

이것은 믹스 인을 사용하는 안전한 내비게이션 운영자를위한 흥미로운 솔루션이었습니다.

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

나는 이것을 훨씬 더 쉽게 사용할 수있는 패키지를 만들었습니다.

NPM jsdig Github jsdig

간단한 것들과 객체를 처리 할 수 ​​있습니다.

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

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

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

또는 조금 더 복잡합니다.

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

-6

개인적으로 사용합니다

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

예를 들어 안전한 얻을 :

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

2
먼저 eval을 사용해서는 안됩니다 . 둘째로 이것은 작동하지 않습니다 : e({a:{b:{c:{d:'test'}}}}, 'a.b.c.d')returns null.
Pylinux

@Pylinux 기본적으로 작동 할 것입니다 e = eval, var a = eval('obj.a.b.c.d'). eval두 번째 매개 변수도 사용하지 않습니다 ... developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/…
Dorian
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.