Có một toán tử hợp nhất null (Elvis) hoặc toán tử điều hướng an toàn trong javascript không?


209

Tôi sẽ giải thích bằng ví dụ:

Toán tử Elvis (? :)

"Toán tử Elvis" là sự rút ngắn của toán tử tạm thời của Java. Một ví dụ về việc tiện dụng này là để trả về giá trị 'mặc định hợp lý' nếu một biểu thức chuyển thành false hoặc null. Một ví dụ đơn giản có thể trông như thế này:

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

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

Toán tử điều hướng an toàn (?.)

Toán tử Điều hướng an toàn được sử dụng để tránh NullPulumException. Thông thường khi bạn có một tham chiếu đến một đối tượng, bạn có thể cần xác minh rằng nó không rỗng trước khi truy cập các phương thức hoặc thuộc tính của đối tượng. Để tránh điều này, toán tử điều hướng an toàn sẽ chỉ trả về null thay vì ném một ngoại lệ, như vậy:

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
'Toán tử Elvis' tồn tại trong C # - nhưng nó được gọi là toán tử liên kết null (ít thú vị hơn) :-)
Cameron

Nếu bạn muốn một cú pháp thay thế, bạn có thể xem qua cofeescript
Lime

Câu hỏi này là một mớ hỗn độn ... nó đang trộn lẫn 3 toán tử khác nhau? : (toán tử ternery, đánh vần trong câu hỏi, có thể là một lỗi đánh máy), ?? (null liên kết, tồn tại trong JavaScript) và ?. (Elvis) KHÔNG tồn tại trong JavaScript. Các câu trả lời không làm rõ sự khác biệt này rất tốt.
JoelFan

2
@JoelFan bạn có thể cung cấp một liên kết đến tài liệu liên quan đến null hợp tác ( ??) trong javascript không? Tất cả mọi thứ tôi tìm thấy cho đến nay cho thấy rằng JS chỉ có "falsey" kết hợp (sử dụng ||).
Charles Wood

1
Chà, tôi không có ý nói rằng JS thực sự có ?? nhưng nó không hợp tác ... nhưng ngay cả ở đó tôi cũng đã sai. Điều đó đang được nói, tôi đã thấy rất nhiều mã JS sử dụng | | như một sự kết hợp vô giá trị, bất chấp những cạm bẫy của chim ưng
JoelFan

Câu trả lời:


138

Bạn có thể sử dụng toán tử 'OR' logic thay cho toán tử Elvis:

Ví dụ displayname = user.name || "Anonymous" .

Nhưng Javascript hiện không có chức năng khác. Tôi khuyên bạn nên nhìn vào CoffeeScript nếu bạn muốn một cú pháp thay thế. Nó có một số tốc ký tương tự như những gì bạn đang tìm kiếm.

Ví dụ: Toán tử tồn tại

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

Phím tắt chức năng

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

Gọi chức năng gợi cảm

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

Ngoài ra còn có các bình luận và lớp học đa dòng. Rõ ràng là bạn phải biên dịch nó thành javascript hoặc chèn vào trang như <script type='text/coffeescript>'nhưng nó bổ sung rất nhiều chức năng :). Sử dụng <script type='text/coffeescript'>thực sự chỉ dành cho phát triển và không sản xuất.


14
logic hoặc không hoàn toàn là điều cần thiết trong hầu hết các trường hợp, vì bạn có thể muốn nó chọn toán hạng bên phải chỉ khi bên trái không được xác định nhưng không phải khi nó được xác định và giả mạo.
dùng2451227

Đó là sai lầm của tôi, hay nó thực sự <script type='coffee/script>'?
JCCM 7/07/2015

2
Trang chủ CoffeeScript sử dụng <script type="text/coffeescript">.
Elias Zamaria

19
Trong khi điều này trả lời câu hỏi, nó gần như hoàn toàn về coffeescript chứ không phải javascript, và hơn một nửa về việc mô tả lợi ích của coffeescript không liên quan đến OP. Tôi khuyên bạn nên đun sôi nó xuống chỉ những gì liên quan đến câu hỏi, cũng tuyệt vời như những lợi ích khác của cà phê.
jinglểula

4
Tôi đang đi chuối à? Chắc chắn sự phản đối của user2451227 (hiện có 4 phiếu) là không hợp lệ vì toán hạng giữa của ternary (tức là toán hạng phải với toán tử Elvis) sẽ không được chọn nếu biểu thức / toán hạng trái được xác định và sai. Trong cả hai trường hợp sau đó bạn phải đi x === undefined.
mike gặm nhấm

114

Tôi nghĩ rằng những điều sau đây tương đương với toán tử điều hướng an toàn, mặc dù lâu hơn một chút:

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

streetNamesau đó sẽ là giá trị của user.address.streethoặc undefined.

Nếu bạn muốn nó mặc định thành thứ khác, bạn có thể kết hợp với phím tắt ở trên hoặc để cung cấp:

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

7
cộng với một ví dụ tuyệt vời về cả lan truyền null và hợp nhất null!
Jay Wick

1
điều này hoạt động ngoại trừ việc bạn sẽ không biết nếu bạn nhận được null hoặc không xác định được từ đó
Dave Cousineau

82

Toán tử OR logic của Javascript bị đoản mạch và có thể thay thế toán tử "Elvis" của bạn:

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

Tuy nhiên, theo hiểu biết của tôi thì không có gì tương đương với ?.nhà điều hành của bạn .


13
+1, tôi quên ||có thể được sử dụng theo cách đó. Hãy nhận biết rằng điều này sẽ kết thành một khối không chỉ khi biểu thức là null, nhưng cũng có khi nó không xác định, false, 0, hoặc chuỗi rỗng.
Cameron

@Cameron, thực sự, nhưng điều đó được đề cập trong câu hỏi và dường như là ý định của người hỏi. ""hoặc 0có thể là bất ngờ, mặc dù :)
Frédéric Hamidi

72

Thỉnh thoảng tôi thấy thành ngữ sau hữu ích:

a?.b?.c

có thể được viết lại thành:

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

Điều này lợi dụng thực tế là việc có được các thuộc tính không xác định trên một đối tượng trả về không xác định, thay vì ném một ngoại lệ như trên nullhoặc undefinedvì vậy chúng tôi thay thế null và không xác định bằng một đối tượng trống trước khi điều hướng.


14
Chà, thật khó để đọc nhưng nó tốt hơn &&phương pháp dài dòng đó . +1.
hét

1
Đó thực sự là toán tử an toàn thực sự trong javascript. Toán tử 'OR' logic được đề cập ở trên là một cái gì đó khác.
vasilakisfil

@Filippos bạn có thể đưa ra một ví dụ về hành vi khác nhau trong phương pháp OR vs && không? Tôi không thể nghĩ về một sự khác biệt
Hạt đậu đỏ

Nó cũng cho phép điều hướng một giá trị ẩn danh mà không cần gán nó cho một biến trước tiên.
Matt Jenkins

1
Yêu nó! Thực sự hữu ích nếu bạn muốn lấy thuộc tính của một đối tượng sau một hoạt động mảng.find () có thể không trả về bất kỳ kết quả nào
Shiraz

24

tôi nghĩ rằng lodash _.get()có thể giúp ở đây, như trong _.get(user, 'name'), và các nhiệm vụ phức tạp hơn như_.get(o, 'a[0].b.c', 'default-value')


5
Vấn đề chính của tôi với phương pháp này là thực tế là vì tên của các thuộc tính là chuỗi, bạn không thể sử dụng các chức năng tái cấu trúc của IDE với độ tin cậy 100% nữa
RPDeshaies

21

Hiện tại có một dự thảo spec:

https://github.com/tc39/proposed-optional-chained

https://tc39.github.io/proposed-optional-chained/

Tuy nhiên, hiện tại, tôi thích sử dụng lodashget(object, path [,defaultValue]) hoặc dlvdelve(obj, keypath)

Cập nhật (kể từ ngày 23 tháng 12 năm 2019):

chuỗi tùy chọn đã chuyển sang giai đoạn 4


Lodash làm cho lập trình trong javascript trở nên ngon miệng hơn
tắc kè

2
chuỗi tùy chọn vừa mới chuyển sang giai đoạn 4 , vì vậy chúng ta sẽ thấy nó trong ES2020
Nick Parsons

1
@NickParsons Cảm ơn! Đã cập nhật câu trả lời.
Jack Tuck

18

Cập nhật 2019

JavaScript hiện có tương đương cho cả Toán tử Elvis và Toán tử Điều hướng An toàn.


Truy cập tài sản an toàn

Các nhà điều hành chaining tùy chọn ( ?.) hiện là một giai đoạn 4 ECMAScript đề nghị . Bạn có thể sử dụng nó ngay hôm nay với Babel .

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

Các logic AND điều hành ( &&) là "cũ", cách hơn-verbose để xử lý tình huống này.

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

Cung cấp một mặc định

Các nhà điều hành coalescing nullish ( ??) hiện đang là giai đoạn 3 ECMAScript đề nghị . Bạn có thể sử dụng nó ngay hôm nay với Babel . Nó cho phép bạn đặt một giá trị mặc định nếu phía bên trái của toán tử là một giá trị null ( 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';

Các nhà điều hành OR logic ( ||) là một giải pháp thay thế với hành vi hơi khác nhau . Nó cho phép bạn thiết lập một giá trị mặc định nếu phía bên trái tay của người điều khiển là falsy . Lưu ý rằng kết quả myVariable3dưới đây khác với myVariable3ở trê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
Câu trả lời này cần nhiều upvote hơn. Nullish coalescing điều hành hiện đang ở giai đoạn 4.
Yerke

13

Đối với trước đây, bạn có thể sử dụng ||. Toán tử "logic hoặc" Javascript, thay vì chỉ trả về các giá trị đúng và sai đóng hộp, tuân theo quy tắc trả về đối số bên trái của nó nếu nó đúng, và nếu không thì đánh giá và trả về đối số đúng của nó. Khi bạn chỉ quan tâm đến giá trị thật, nó cũng hoạt động như nhau, nhưng điều đó cũng có nghĩa là foo || bar || baztrả về giá trị bên trái của foo, bar hoặc baz chứa giá trị thực .

Tuy nhiên, bạn sẽ không tìm thấy cái nào có thể phân biệt false với null, và 0 và chuỗi rỗng là các giá trị sai, vì vậy hãy tránh sử dụng value || defaultcấu trúc trong đó valuecó thể là 0 hoặc "".


4
Công việc tốt lưu ý rằng điều này có thể dẫn đến hành vi không mong muốn khi toán hạng bên trái là giá trị falsey không null.
Shog9

11

Có, có! 🍾

Chuỗi tùy chọn là trong giai đoạn 4 và điều này cho phép bạn sử dụng user?.address?.streetcông thức.

Nếu bạn không thể đợi bản phát hành, hãy cài đặt @babel/plugin-proposal-optional-chainingvà bạn có thể sử dụng nó. Dưới đây là các cài đặt phù hợp với tôi hoặc chỉ đọc bài viết của 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
Anh ấy hỏi nếu có, không phải là bạn có thể thêm không. Tôi nghĩ rằng điều này không hữu ích khi xem xét nó không phải là những gì được yêu cầu.
DeanMWake

2
Nó đạt đến giai đoạn 3 của quy trình tiêu chuẩn hóa ECMAScript. es2020 - babeljs.io/docs/en/babel-plugin-proposed-optional-chained
wedi

Tôi nghĩ rằng câu trả lời này là sai lệch như là.
Leonardo Raele

1
Câu trả lời này không hoàn toàn chính xác! Chuỗi tùy chọn vẫn còn trong giai đoạn 3 và ES2020 chưa được phát hành hoặc thậm chí chưa hoàn tất. Ít nhất bạn đã đề cập đến cách người ta có thể sử dụng nó mà không phải chờ đợi nó được phát hành.
Maxie Berkmann

@gazdagergo Không vấn đề gì :).
Maxie Berkmann

6

Đây là một toán tử elvis đơn giản tương đương:

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

CẬP NHẬT THÁNG 9 NĂM 2019

Vâng, bây giờ JS hỗ trợ này. Sắp có chuỗi tùy chọn đến sớm để v8 đọc thêm


Không giống lắm. OP là về kết hợp null, nhưng dù sao câu trả lời tốt đẹp.
Maxie Berkmann

4

Điều này thường được biết đến như là một toán tử hợp nhất null. Javascript không có.


3
đúng theo nghĩa nghiêm ngặt, nhưng như các câu trả lời khác đã lưu ý toán tử OR logic của JavaScript có thể hoạt động như một toán tử kết hợp sai , cho phép bạn đạt được cùng một sự ngắn gọn trong nhiều tình huống.
Shog9

1
Đây không phải là một toán tử hợp nhất null. Không hợp nhất chỉ hoạt động trên một giá trị duy nhất, không phải trên một chuỗi các quyền truy cập / chức năng truy cập thuộc tính. Bạn đã có thể kết hợp null với toán tử OR logic trong JavaScript.

Không, bạn có thể kết hợp sai với OR logic trong JavaScript.
andresp

3

Bạn có thể đạt được hiệu quả tương tự bằng cách nói:

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

2

Tôi có một giải pháp cho điều đó, điều chỉnh nó theo nhu cầu của riêng bạn, một đoạn trích từ một trong những lib của tôi:

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

hoạt động như một lá bùa. Tận hưởng nỗi đau ít hơn!


Có vẻ hứa hẹn, bạn có thể gửi xin vui lòng nguồn đầy đủ? bạn có nó ở bất cứ nơi nào công cộng? (ví dụ: GitHub)
Eran Medan

1
Tôi sẽ tạo một đoạn trích nhỏ từ mã tôi sử dụng và sẽ đăng nó trên GitHub sau một tuần nữa.
balazstth

2

Bạn có thể tự cuộn:

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

Và sử dụng nó như thế này:

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

* kết quả là không xác định nếu bất kỳ một trong a, b, c hoặc d không được xác định.


1

Tôi đọc bài viết này ( https://www.beyondjava.net/elvis-operator-aka-safe-navestion-javascript-typescript ) và sửa đổi giải pháp bằng Proxy.

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

Bạn gọi nó như thế này:

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

Kết quả sẽ không được xác định cho một biểu thức gặp null hoặc không xác định dọc theo đường dẫn của nó. Bạn có thể phát điên và sửa đổi nguyên mẫu Object!

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

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


1

Đây là một vấn đề đối với tôi trong một thời gian dài. Tôi đã phải đưa ra một giải pháp có thể dễ dàng di chuyển một khi chúng tôi có được nhà điều hành Elvis hoặc một cái gì đó.

Đây là những gì tôi sử dụng; hoạt động cho cả mảng và đối tượng

đặt cái này vào tập tin tools.js hoặc cái gì đó

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

ví dụ sử dụng:

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

tôi biết nó làm cho mã hơi khó đọc nhưng nó là một giải pháp đơn giản và hoạt động rất tốt. Tôi hi vọng nó giúp ích cho ai đó :)


0

Đây là một giải pháp thú vị cho người vận hành điều hướng an toàn bằng cách sử dụng một số mixin ..

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

Tôi đã tạo ra một gói làm cho điều này dễ sử dụng hơn rất nhiều.

NPM jsdig Github jsdig

Bạn có thể xử lý những thứ đơn giản như và đối tượng:

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

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

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

hoặc phức tạp hơn một chút:

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

-6

Cá nhân tôi sử dụng

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

và ví dụ an toàn có được:

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

2
Đầu tiên bạn thực sự không nên sử dụng eval . Thứ hai, điều này thậm chí không hoạt động: e({a:{b:{c:{d:'test'}}}}, 'a.b.c.d')trả về null.
Pylinux

@Pylinux cơ bản những gì sẽ làm việc là e = eval, var a = eval('obj.a.b.c.d'). evalthậm chí không lấy tham số thứ hai ... developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/
Kẻ
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.