Truy cập tài sản không an toàn (và chuyển nhượng có điều kiện) trong ES6 / 2015


149

Có một nulltoán tử truy cập thuộc tính an toàn (tuyên truyền / tồn tại null) trong ES6 (ES2015 / JavaScript.next / Harmony) như ?.trong CoffeeScript chẳng hạn? Hay là nó được lên kế hoạch cho ES7?

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

Điều này sẽ đại khái như:

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

Lý tưởng nhất là giải pháp không nên gán (chẵn undefined) cho aThingnếu possiblyNullnull


3
@naomik Loại kiểm tra null này có thể rất hữu ích nếu các câu lệnh mà bạn đang kiểm tra một thuộc tính được lồng sâu, ví dụ if( obj?.nested?.property?.value )thay vìif( obj && obj.nested && obj.nested.property && obj.nested.property.value )
Sean Walsh

@SeanWalsh nếu các đối tượng của bạn được lồng sâu hoặc nếu các chức năng của bạn đào sâu vào các đối tượng của bạn, có thể có một số vấn đề khác với ứng dụng của bạn.
Cảm ơn bạn

1
so sánh var appConfig = loadConfig(config, process.env); connect(appConfig.database);với connect(config). Bạn có thể truyền một đối tượng đơn giản hơn nhiều connectthay vì truyền toàn bộ configđối tượng, bạn có thể sử dụng conf.username, conf.passwordthay vì thử một cái gì đó như config[process.env]?.database?.username, config[process.env]?.database?.password. Tham khảo: Luật của Demeter .
Cảm ơn bạn

Ngoài ra, nếu bạn làm một cái gì đó như đặt mặc định hoặc khử trùng các thuộc tính (điều này có thể được thực hiện trong loadConfigví dụ trên), bạn có thể đưa ra các giả định về sự tồn tại của các thuộc tính và bỏ qua việc kiểm tra null trong vô số khu vực của ứng dụng.
Cảm ơn bạn

4
@naomik Miễn là ngôn ngữ hỗ trợ các đối tượng lồng nhau, nó vẫn là một tính năng hữu ích - bất kể bạn hoặc tôi nghĩ gì về kiến ​​trúc của chính ứng dụng. Bên cạnh đó, các biểu đồ đối tượng phức tạp như thế này rất phổ biến trong các ORM đang mô hình hóa một mô hình dữ liệu phức tạp.
Sean Walsh

Câu trả lời:


98

Cập nhật (2020-01-31): Có vẻ như mọi người vẫn đang tìm thấy điều này, đây là câu chuyện hiện tại:

Cập nhật (2017-08-01): Nếu bạn muốn sử dụng một plugin chính thức, bạn có thể thử bản dựng alpha của Babel 7 với biến đổi mới. Số dặm của bạn có thể thay đổi

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

Bản gốc :

Một tính năng hoàn thành hiện đang ở giai đoạn 1: Chuỗi tùy chọn.

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

Nếu bạn muốn sử dụng nó ngay hôm nay, có một plugin Babel thực hiện điều đó.

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


Tôi có hiểu chính xác rằng điều đó không bao gồm chuyển nhượng có điều kiện không? street = user.address?.streetsẽ đặt streettrong trường hợp nào?
ᆼ ᆺ

1
Thật không may, tôi nghĩ rằng bạn đúng. Street Tôi nghĩ rằng sẽ được chỉ định undefined. Nhưng ít nhất nó sẽ không cố gắng truy cập vào các thuộc tính không xác định.
basicdays

2
Có vẻ như bạn có thể là người điều khiển bên tay trái và nếu nó không có gì để xác định, thì bên phải sẽ không được đánh giá. Plugin babel có thể thay đổi một chút, bản thân tôi chưa thử nghiệm nó nhiều.
basicdays

1
Về chuyển nhượng có điều kiện ở phía bên trái của =, có vẻ như điều đó không được hỗ trợ trong thông số chính thức hiện tại. github.com/tc39/proposal-optional-chaining#not-supported
basicdays

1
Kể từ tháng 5 năm 2020, có vẻ như các trình duyệt và Bản đánh máy hiện tại đã triển khai việc này!
Josh Diehl

65

Nó không đẹp như? toán tử, nhưng để đạt được kết quả tương tự bạn có thể làm:

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

Kể từ nullundefinedđều falsy giá trị ( xem tài liệu tham khảo này ), tài sản sau khi &&khai thác được chỉ được truy cập nếu tiền lệ nó không phải null hoặc không xác định.

Ngoài ra, bạn có thể viết một chức năng như thế này:

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

Sử dụng:

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

Hoặc, với giá trị dự phòng:

_try(() => user.address.postcode, "none") // return postcode or a custom string

Có lẽ tôi nên làm rõ câu hỏi, điều chính tôi đã làm sau đó là sự phân công có điều kiện
ᆼ ᆺ

chỉ là một hệ quả tất yếu; để tìm nghịch đảo một cách an toàn, bạn có thể sử dụng !(user && user.address && user.address.postcode) :)
rob2d

foo && foo.bar && foo.bar.quux ...trong một cơ sở mã lớn, điều này thật xấu xí và thêm nhiều phức tạp mà bạn nên tránh.
Skylar Saveland

1
Đây là giải pháp sạch nhất tôi tìm thấy trên internet. Tôi sử dụng điều này với bản đánh máy:_get<T>(func: () => T, fallbackValue?: T) :T
tomwassing

3
Nếu user.address.postcodekhông xác định, _try(() => user.address.postcode, "")sẽ trở lại undefinedthay vì "". Vì vậy, mã _try(() => user.address.postcode, "").lengthsẽ đưa ra một ngoại lệ.
Alexander Chen

33

Không. Bạn có thể sử dụng lodash # get hoặc một cái gì đó tương tự cho điều này trong JavaScript.


4
Có đề xuất để thêm nó vào ES7?
ᆼ ᆺ

1
Không gặp phải bất kỳ.
Girafa

3
@PeterVarga: Nhiều. Thảo luận quá nhiều. Ngữ pháp không phải là một điều dễ dàng cho tính năng này
Bergi

1
lodash.gethơi khác một chút ở chỗ nó không thể làm bài tập có điều kiện.
ᆼ ᆺ

17

Vanilla thay thế để truy cập tài sản an toàn

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

Nhiệm vụ có điều kiện ngắn gọn nhất có lẽ sẽ là

try { b = a.b.c.d.e } catch(e) {}

Vì vậy, làm thế nào bạn sẽ viết bài tập trong câu hỏi bằng cách sử dụng cơ chế này? Bạn vẫn không cần một điều kiện để không gán trong trường hợp possiblyNullkhông được xác định / null?
ᆼ ᆺ

Chắc chắn, bạn vẫn cần kiểm tra xem bạn có muốn một bài tập xảy ra hay không. Nếu bạn sử dụng toán tử '=', một cái gì đó chắc chắn sẽ được chỉ định, có thể là dữ liệu, không xác định hoặc null. Vì vậy, ở trên chỉ là một truy cập tài sản an toàn. Có toán tử gán điều kiện thậm chí tồn tại, trong bất kỳ ngôn ngữ?
vẫy tay vào

Tất nhiên, ví dụ CoffeeScript , nó cũng có||=
ᆼ ᆺ ᆼ

+1 Ngay cả suy nghĩ đầu tiên của tôi là (((a.b || {}).c || {}).d || {}).e. Nhưng chính xác hơn nó sẽ là((((a || {}).b || {}).c || {}).d || {}).e
IsmailS

6

Không, không có toán tử lan truyền null trong ES6. Bạn sẽ phải đi với một trong những mẫu đã biết .

Bạn có thể sử dụng phá hủy, mặc dù:

({thing: aThing} = possiblyNull);

Có nhiều cuộc thảo luận (ví dụ như điều này ) để thêm một toán tử như vậy trong ES7, nhưng không có cuộc thảo luận nào thực sự diễn ra.


Điều đó có vẻ đầy hứa hẹn, tuy nhiên ít nhất những gì Babel làm với nó không có gì khác biệt so với chỉaThing = possiblyNull.thing
ᆼ ᆺ

1
@PeterVarga: Rất tiếc, bạn đúng, phá hủy hoạt động khi tài sản không tồn tại, nhưng không phải khi đối tượng là null. Bạn sẽ phải cung cấp một giá trị mặc định, khá giống mẫu này nhưng với cú pháp khó hiểu hơn.
Bergi

4

Đi theo danh sách ở đây , hiện tại không có đề xuất nào để thêm giao dịch an toàn vào Ecmascript. Vì vậy, không chỉ không có cách hay để làm điều này, mà nó sẽ không được thêm vào trong tương lai gần.


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

1

Tùy chọn Chaining ?.và Nullish Coalescing??

Bây giờ bạn có thể trực tiếp sử dụng ?.nội tuyến để kiểm tra sự tồn tại một cách an toàn. Tất cả các trình duyệt hiện đại đều hỗ trợ nó.

?? có thể được sử dụng để đặt giá trị mặc định nếu không xác định hoặc null.

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

Nếu một tài sản tồn tại, ?.tiến hành kiểm tra tiếp theo hoặc trả về giá trị hợp lệ. Bất kỳ thất bại sẽ ngay lập tức ngắn mạch và trở lại 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

Để đảm bảo giá trị được xác định mặc định, bạn có thể sử dụng ??. Nếu bạn yêu cầu giá trị trung thực đầu tiên, bạn có thể sử dụng ||.

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

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

Nếu bạn không kiểm tra trường hợp, thuộc tính bên trái phải tồn tại. Nếu không, nó sẽ ném một ngoại lệ.

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

?.Hỗ trợ trình duyệt - 78%, tháng 7 năm 2020

??Hỗ trợ trình duyệt - 78%

Tài liệu Mozilla

-

Phân công logic nullish, giải pháp 2020+

Các nhà khai thác mới hiện đang được thêm vào các trình duyệt ??=, ||=&&=. Họ không làm những gì bạn đang tìm kiếm, nhưng có thể dẫn đến kết quả tương tự tùy thuộc vào mục tiêu của mã của bạn.

Chú ý: Đây không phải là phổ biến ở các phiên bản trình duyệt nào được nêu ra , nhưng Babel nên transpile tốt. Sẽ cập nhật khi thay đổi sẵn có.

??=kiểm tra nếu bên trái là không xác định hoặc null, ngắn mạch nếu đã được xác định. Nếu không, bên trái được gán giá trị bên phải. ||=&&=tương tự, nhưng dựa trên ||và các &&nhà khai thác.

Ví dụ cơ bản

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

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

Ví dụ đối tượng / mảng

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

Hỗ trợ trình duyệt tháng 7 năm 2020 - 0,03%

Tài liệu Mozilla


1
Câu hỏi cũng là về sự phân công có điều kiện
ᆼ ᆺ

Đã thêm một vài ví dụ với ?.??, và một giải pháp chi tiết sắp tới có thể phù hợp với tình huống của bạn. Giải pháp tốt nhất hiện tại có lẽ là chỉ cần làmaThing = possiblyNull?.thing ?? aThing
Gibolt

0

Một phương thức nhận sâu an toàn có vẻ như phù hợp tự nhiên với underscore.js nhưng vấn đề là tránh lập trình chuỗi. Sửa đổi câu trả lời của @ Felipe để tránh lập trình chuỗi (hoặc ít nhất là đẩy các trường hợp cạnh trở lại cho người gọi):

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

Thí dụ:

var test = { 
  a: { 
    b: 'b property value',
    c: { }
  } 
}
safeGet(test, ['a', 'b']) 
safeGet(test, "a.b".split('.'))  

Để diễn giải Rick: điều này nghe giống như lập trình chuỗi với các bước bổ sung.
tocqueville

1
lodash hiện thực hiện get / set sâu _.get(obj, array_or_dotstring)
nguyên mẫu

1
Nhưng để công bằng, ngay cả ký hiệu truy cập chuỗi và dấu chấm Javascript về cơ bản là lập trình chuỗi, obj.a.b.cso vớiobj['a']['b']['c']
nguyên mẫu

1
Nơi keysđến từ đâu?
Levi Roberts

-2

Tôi biết đây là một câu hỏi JavaScript, nhưng tôi nghĩ Ruby xử lý vấn đề này theo tất cả các cách được yêu cầu, vì vậy tôi nghĩ đó là một điểm tham chiếu có liên quan.

.&, tryvà && có những điểm mạnh và những cạm bẫy tiềm năng. Rất nhiều lựa chọn tuyệt vời ở đây: http://mitrev.net/ruby/2015/11/13/the-operator-in-ruby/

TLDR; Kết luận của Rubyists diglà vừa dễ nhìn hơn vừa đảm bảo mạnh mẽ hơn rằng giá trị hoặc nullsẽ được chỉ định.

Đây là một cải tiến đơn giản trong 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
}

Điều này có thể được sử dụng cho bất kỳ độ sâu của chức năng lồng và xử lý.

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

Cách trytiếp cận cũng tốt đẹp để đọc trong JS, như được hiển thị trong các câu trả lời trước. Nó cũng không yêu cầu lặp, đó là một nhược điểm của việc thực hiện này.


Tôi đặt câu trả lời này lên đây như một giải pháp thú vị / đầy đủ / thay thế b / c Tôi thích cách ruby ​​thực hiện điều này với đường cú pháp. Chỉ cần đưa nó ra ngoài cộng đồng - Bỏ phiếu một lần nữa và tôi sẽ vui vẻ xóa bài đăng này. Chúc mừng!
theUtherSide

Thật tuyệt khi thấy ECMAScript2019 có một đề xuất tuyệt vời cho "Chuỗi tùy chọn" giống như Ruby: github.com/tc39/proposed-optional-chained Hôm nay, nó có sẵn thông qua Plugin Babel và nó cũng hoạt động để gọi các phương thức trên một đối tượng: babeljs.io / docs / en / babel-plugin-đề nghị-tùy chọn-xích
theUtherSide

-5

Tôi nghĩ rằng câu hỏi này cần một chút làm mới cho năm 2018. Điều này có thể được thực hiện độc đáo mà không cần bất kỳ thư viện nào sử dụng Object.defineProperty()và có thể được sử dụng như sau:

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

Tôi coi điều này là an toàn (và js-ethical) vì các định nghĩa writeableenumerablehiện có sẵn cho definePropertyphương pháp Object, như được ghi trong MDN

định nghĩa hàm dưới đây:

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

Tôi đã kết hợp một jsBin với đầu ra giao diện điều khiển để chứng minh điều này. Lưu ý rằng trong phiên bản jsBin, tôi cũng đã thêm một ngoại lệ tùy chỉnh cho các giá trị trống. Đây là tùy chọn và vì vậy tôi đã bỏ nó khỏi định nghĩa tối thiểu ở trên.

Cải tiến được hoan nghênh


2
Với điều này, bạn đang thực sự viết mã thành chuỗi. Đó thực sự là một ý tưởng tồi để làm điều đó. Nó làm cho mã của bạn không thể cấu trúc lại và không thân thiện với IDE.
tocqueville
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.