Kiểm tra sự tồn tại của khóa đối tượng JavaScript lồng nhau


691

Nếu tôi có một tham chiếu đến một đối tượng:

var test = {};

điều đó sẽ có khả năng (nhưng không phải ngay lập tức) có các đối tượng lồng nhau, đại loại như:

{level1: {level2: {level3: "level3"}}};

Cách tốt nhất để kiểm tra sự tồn tại của tài sản trong các đối tượng được lồng sâu là gì?

alert(test.level1);sản lượng undefined, nhưng alert(test.level1.level2.level3);thất bại.

Tôi hiện đang làm một cái gì đó như thế này:

if(test.level1 && test.level1.level2 && test.level1.level2.level3) {
    alert(test.level1.level2.level3);
}

nhưng tôi đã tự hỏi nếu có một cách tốt hơn.


1
bạn có thể muốn kiểm tra một câu hỏi liên quan đến tiếp tuyến đã được hỏi gần đây stackoverflow.com/questions/2525943/
mẹo


Một vài đề xuất ở đó: stackoverflow.com/a/18381564/1636522

Cách tiếp cận hiện tại của bạn có một vấn đề tiềm ẩn nếu thuộc tính level3 là sai, trong trường hợp đó, ngay cả khi tài sản tồn tại sẽ truy xuất nfalse, hãy xem ví dụ này, vui lòng jsfiddle.net/maz9bLjx
GibboK

10
đơn giản là bạn có thể sử dụng thử bắt cũng được
Raghavendra

Câu trả lời:


487

Bạn phải làm điều đó từng bước nếu bạn không muốn TypeErrorbởi vì nếu một trong số các thành viên là nullhoặc undefined, và bạn cố gắng truy cập một thành viên, một ngoại lệ sẽ được đưa ra.

Bạn có thể chỉ đơn giản catchlà ngoại lệ hoặc tạo một hàm để kiểm tra sự tồn tại của nhiều cấp độ, đại loại như thế này:

function checkNested(obj /*, level1, level2, ... levelN*/) {
  var args = Array.prototype.slice.call(arguments, 1);

  for (var i = 0; i < args.length; i++) {
    if (!obj || !obj.hasOwnProperty(args[i])) {
      return false;
    }
    obj = obj[args[i]];
  }
  return true;
}

var test = {level1:{level2:{level3:'level3'}} };

checkNested(test, 'level1', 'level2', 'level3'); // true
checkNested(test, 'level1', 'level2', 'foo'); // false

CẬP NHẬT ES6:

Đây là phiên bản ngắn hơn của chức năng ban đầu, sử dụng các tính năng và đệ quy ES6 (nó cũng ở dạng gọi đuôi thích hợp ):

function checkNested(obj, level,  ...rest) {
  if (obj === undefined) return false
  if (rest.length == 0 && obj.hasOwnProperty(level)) return true
  return checkNested(obj[level], ...rest)
}

Tuy nhiên, nếu bạn muốn lấy giá trị của một thuộc tính lồng nhau và không chỉ kiểm tra sự tồn tại của nó, đây là một hàm đơn giản:

function getNested(obj, ...args) {
  return args.reduce((obj, level) => obj && obj[level], obj)
}

const test = { level1:{ level2:{ level3:'level3'} } };
console.log(getNested(test, 'level1', 'level2', 'level3')); // 'level3'
console.log(getNested(test, 'level1', 'level2', 'level3', 'length')); // 6
console.log(getNested(test, 'level1', 'level2', 'foo')); // undefined
console.log(getNested(test, 'a', 'b')); // undefined

Hàm trên cho phép bạn lấy giá trị của các thuộc tính lồng nhau, nếu không sẽ trả về undefined.

CẬP NHẬT 2019-10-17:

Các đề nghị chaining bắt buộc đạt Giai đoạn 3 trên quá trình ủy ban ECMAScript , điều này sẽ cho phép bạn truy cập một cách an toàn tính lồng nhau sâu sắc, bằng cách sử dụng các dấu hiệu ?., mới khai thác chaining tùy chọn :

const value = obj?.level1?.level2?.level3 

Nếu bất kỳ mức nào được truy cập là nullhoặc undefinedbiểu thức sẽ tự giải quyết undefined.

Đề xuất cũng cho phép bạn xử lý các cuộc gọi phương thức một cách an toàn:

obj?.level1?.method();

Biểu thức trên sẽ tạo ra undefinednếu obj, obj.level1hoặc obj.level1.methodnullhay undefined, nếu không nó sẽ gọi hàm.

Bạn có thể bắt đầu chơi với tính năng này với Babel bằng cách sử dụng plugin chuỗi tùy chọn .

Kể từ Babel 7.8.0 , ES2020 được hỗ trợ theo mặc định

Kiểm tra ví dụ này trên REPL Babel.

UPDATE: Tháng 12 năm 2019

Đề xuất xích tùy chọn cuối cùng đã đạt đến Giai đoạn 4 trong cuộc họp tháng 12 năm 2019 của ủy ban TC39. Điều này có nghĩa là tính năng này sẽ là một phần của Tiêu chuẩn ECMAScript 2020 .


4
argumentskhông thực sự là một mảng. Array.prototype.slice.call(arguments)chuyển đổi nó thành một mảng chính thức. Tìm hiểu
deefour

23
việc này sẽ hiệu quả hơn rất nhiều để làm var obj = arguments[0];và bắt đầu từ var i = 1thay vì sao chép argumentsđối tượng
Claudiu

2
Tôi kết hợp một phiên bản với thử / bắt vì mục đích thắt lưng buộc bụng, và không có gì ngạc nhiên - hiệu suất là khủng khiếp (ngoại trừ trong Safari vì một số lý do). Có một số câu trả lời dưới đây khá hiệu quả, cùng với sửa đổi của Claudiu cũng hiệu quả hơn đáng kể so với câu trả lời được chọn. Xem jsperf tại đây jsperf.com/check-if-deep-property-exists-with-willnotthrow
netpoetica

3
Trong ES6, argskhai báo biến có thể được loại bỏ và ...args có thể được sử dụng làm đối số thứ hai cho checkNestedphương thức. developer.mozilla.org/en/docs/Web/JavaScript/Reference/,
Vernon

6
Đây là một điều rất không thể nhầm lẫn. Nếu bất kỳ khóa thuộc tính nào thay đổi (chúng sẽ), tất cả các nhà phát triển trong dự án sẽ phải 'tìm kiếm chuỗi' toàn bộ cơ sở mã. Đây thực sự không phải là một giải pháp cho vấn đề này, vì nó giới thiệu một vấn đề lớn hơn nhiều
Drenai

356

Đây là một mẫu tôi đã chọn từ Oliver Steele :

var level3 = (((test || {}).level1 || {}).level2 || {}).level3;
alert( level3 );

Trong thực tế, toàn bộ bài viết là một cuộc thảo luận về cách bạn có thể làm điều này trong javascript. Anh ta quyết định sử dụng cú pháp trên (không khó đọc một khi bạn đã quen với nó) như một thành ngữ.


8
@wared Tôi nghĩ nó thú vị chủ yếu là vì nó ngắn gọn như thế nào. Có một cuộc thảo luận chi tiết về các đặc tính hiệu suất trong bài được liên kết. Có, nó luôn thực hiện tất cả các thử nghiệm, nhưng nó tránh tạo ra các tem tạm thời và bạn có thể đặt bí danh {} cho một var nếu bạn muốn ngăn chặn chi phí tạo một đối tượng trống mới mỗi lần. Trong 99% trường hợp tôi sẽ không mong đợi tốc độ là vấn đề, và trong trường hợp nó không có sự thay thế cho hồ sơ.
Gabe Moothart

9
@MuhammadUmer Không, vấn đề (test || {})là nếu kiểm tra không được xác định, thì bạn đang làm ({}.level1 || {}). Tất nhiên, {}.level1không xác định, vì vậy điều đó có nghĩa là bạn đang làm {}.level2, v.v.
Joshua Taylor

3
@JoshuaTaylor: Tôi nghĩ rằng anh ta có nghĩa là nếu testkhông được khai báo, sẽ có một ReferenceError , nhưng đó không phải là vấn đề, bởi vì nếu nó không được khai báo, có một lỗi cần được sửa, vì vậy lỗi là một điều tốt.

33
bạn đã nói "điều đó không khó đọc một khi bạn đã quen với nó" . Vâng, đây là những dấu hiệu bạn đã biết đây là một mớ hỗn độn . Vậy thì tại sao lại đề xuất giải pháp này? Nó dễ bị lỗi chính tả và hoàn toàn không có gì để dễ đọc. Chỉ cần nhìn nó! Nếu tôi phải viết một dòng xấu xí, nó sẽ có thể đọc được ; vì vậy tôi sẽ chỉ gắn bó vớiif(test.level1 && test.level1.level2 && test.level1.level2.level3)
Sharky

8
Trừ khi tôi thiếu một cái gì đó, điều này sẽ không hoạt động cho các thuộc tính cuối boolean có thể sai ... thật đáng buồn. Nếu không thì tôi yêu thành ngữ này.
T3db0t

261

Cập nhật

Có vẻ như lodash đã thêm _.get cho tất cả các nhu cầu nhận được tài sản lồng nhau của bạn.

_.get(countries, 'greece.sparta.playwright')

https://lodash.com/docs#get


Câu trả lời trước

Người dùng lodash có thể thưởng thức lodash.contrib có một vài phương pháp giúp giảm thiểu vấn đề này .

getPath

Chữ ký: _.getPath(obj:Object, ks:String|Array)

Nhận giá trị ở bất kỳ độ sâu nào trong một đối tượng lồng nhau dựa trên đường dẫn được mô tả bởi các khóa đã cho. Các khóa có thể được cung cấp dưới dạng một mảng hoặc dưới dạng một chuỗi phân tách bằng dấu chấm. Trả về undefinednếu đường dẫn không thể đạt được.

var countries = {
        greece: {
            athens: {
                playwright:  "Sophocles"
            }
        }
    }
};

_.getPath(countries, "greece.athens.playwright");
// => "Sophocles"

_.getPath(countries, "greece.sparta.playwright");
// => undefined

_.getPath(countries, ["greece", "athens", "playwright"]);
// => "Sophocles"

_.getPath(countries, ["greece", "sparta", "playwright"]);
// => undefined

Lodash thực sự cần một phương thức _.isPathDefined (obj, pathString).
Matthew Payne

@MatthewPayne Có lẽ thật tuyệt, nhưng nó thực sự không cần thiết. Bạn có thể tự làm điều đó thật dễ dàngfunction isPathDefined(object, path) { return typeof _.getPath(object, path) !== 'undefined'; }
Thor84no

11
Lodash cũng có chức năng tương tự:_.get(countries, 'greece.sparta.playwright', 'default'); // → 'default' _.has(countries, 'greece.spart.playwright') // → false
Tom

thậm chí tốt hơn sẽ là _.result
Shishir Arora

Nếu bạn cần xác định nhiều con đường khác nhau, hãy cân nhắc: var url = _.get(e, 'currentTarget.myurl', null) || _.get(e, 'currentTarget.attributes.myurl.nodeValue', null) || null
Simon Hutchison

209

Tôi đã thực hiện các bài kiểm tra hiệu suất (cảm ơn cdMinix vì đã thêm lodash) vào một số đề xuất được đề xuất cho câu hỏi này với kết quả được liệt kê bên dưới.

Tuyên bố từ chối trách nhiệm số 1 Biến chuỗi thành tham chiếu là lập trình meta không cần thiết và có lẽ tốt nhất nên tránh. Đừng để mất dấu vết của tài liệu tham khảo của bạn để bắt đầu. Đọc thêm từ câu trả lời này cho một câu hỏi tương tự .

Tuyên bố miễn trừ trách nhiệm số 2 Chúng tôi đang nói về hàng triệu hoạt động mỗi mili giây tại đây. Rất ít khả năng bất kỳ trong số này sẽ tạo ra nhiều khác biệt trong hầu hết các trường hợp sử dụng. Chọn bất cứ điều gì có ý nghĩa nhất khi biết những hạn chế của mỗi. Đối với tôi, tôi sẽ đi với một cái gì đó như reducelà thuận tiện.

Object Wrap (của Oliver Steele) - 34% - nhanh nhất

var r1 = (((test || {}).level1 || {}).level2 || {}).level3;
var r2 = (((test || {}).level1 || {}).level2 || {}).foo;

Giải pháp ban đầu (đề xuất trong câu hỏi) - 45%

var r1 = test.level1 && test.level1.level2 && test.level1.level2.level3;
var r2 = test.level1 && test.level1.level2 && test.level1.level2.foo;

kiểm tra - 50%

function checkNested(obj) {
  for (var i = 1; i < arguments.length; i++) {
    if (!obj.hasOwnProperty(arguments[i])) {
      return false;
    }
    obj = obj[arguments[i]];
  }
  return true;
}

get_if_exist - 52%

function get_if_exist(str) {
    try { return eval(str) }
    catch(e) { return undefined }
}

hợp lệ - 54%

function validChain( object, ...keys ) {
    return keys.reduce( ( a, b ) => ( a || { } )[ b ], object ) !== undefined;
}

objHasKeys - 63%

function objHasKeys(obj, keys) {
  var next = keys.shift();
  return obj[next] && (! keys.length || objHasKeys(obj[next], keys));
}

NestedPropertyExists - 69%

function nestedPropertyExists(obj, props) {
    var prop = props.shift();
    return prop === undefined ? true : obj.hasOwnProperty(prop) ? nestedPropertyExists(obj[prop], props) : false;
}

_ quên - 72%

khử trùng - 86%

function deeptest(target, s){
    s= s.split('.')
    var obj= target[s.shift()];
    while(obj && s.length) obj= obj[s.shift()];
    return obj;
}

chú hề buồn - 100% - chậm nhất

var o = function(obj) { return obj || {} };

var r1 = o(o(o(o(test).level1).level2).level3);
var r2 = o(o(o(o(test).level1).level2).foo);

16
cần lưu ý rằng có bao nhiêu% bài kiểm tra có - SLOWER nó
avalanche1

2
lodash _.get()thì sao? nó thực hiện như thế nào so với những câu trả lời?
beniutek

1
Mỗi phương pháp này chậm hơn hoặc nhanh hơn các phương pháp khác tùy theo tình huống. Nếu tất cả các khóa được tìm thấy thì "Object Wrap" có thể nhanh nhất, nhưng nếu không tìm thấy một trong các khóa thì "Giải pháp gốc / Giải pháp gốc" có thể nhanh hơn.
evilReiko

1
@evilReiko Bất kỳ phương pháp nào sẽ chậm hơn nếu không tìm thấy khóa nào nhưng theo tỷ lệ với nhau, nó vẫn khá giống nhau. Tuy nhiên, bạn đã đúng - đây là một bài tập trí tuệ hơn bất cứ thứ gì khác. Chúng ta đang nói về một triệu lần lặp lại mỗi mili giây ở đây. Tôi thấy không có trường hợp sử dụng mà nó sẽ làm cho nhiều sự khác biệt. Cá nhân tôi sẽ đi reducehoặc try/catchra cho thuận tiện.
unitario

Nó hoạt động như thế nào so vớitry { test.level1.level2.level3 } catch (e) { // some logger e }
Lex

46

Bạn có thể đọc một thuộc tính đối tượng ở bất kỳ độ sâu nào, nếu bạn xử lý tên như một chuỗi : 't.level1.level2.level3'.

window.t={level1:{level2:{level3: 'level3'}}};

function deeptest(s){
    s= s.split('.')
    var obj= window[s.shift()];
    while(obj && s.length) obj= obj[s.shift()];
    return obj;
}

alert(deeptest('t.level1.level2.level3') || 'Undefined');

Nó trả về undefinednếu bất kỳ phân đoạn nào undefined.


3
Đáng lưu ý rằng phương pháp này rất hiệu quả, ít nhất là trong Chrome, trong một số trường hợp vượt trội so với phiên bản sửa đổi @Claudiu của câu trả lời được chọn. Xem kiểm tra hiệu suất tại đây: jsperf.com/check-if-deep-property-exists-with-willnotthrow
netpoetica

28
var a;

a = {
    b: {
        c: 'd'
    }
};

function isset (fn) {
    var value;
    try {
        value = fn();
    } catch (e) {
        value = undefined;
    } finally {
        return value !== undefined;
    }
};

// ES5
console.log(
    isset(function () { return a.b.c; }),
    isset(function () { return a.b.c.d.e.f; })
);

Nếu bạn đang mã hóa trong môi trường ES6 (hoặc sử dụng 6to5 ) thì bạn có thể tận dụng cú pháp hàm mũi tên :

// ES6 using the arrow function
console.log(
    isset(() => a.b.c),
    isset(() => a.b.c.d.e.f)
);

Về hiệu suất, không có hình phạt hiệu suất cho việc sử dụng try..catchkhối nếu thuộc tính được đặt. Có một tác động hiệu suất nếu tài sản không được đặt.

Hãy xem xét đơn giản bằng cách sử dụng _.has:

var object = { 'a': { 'b': { 'c': 3 } } };

_.has(object, 'a');
// → true

_.has(object, 'a.b.c');
// → true

_.has(object, ['a', 'b', 'c']);
// → true

2
Tôi nghĩ rằng try-catchcách tiếp cận là câu trả lời tốt nhất. Có một sự khác biệt về mặt triết học giữa việc truy vấn một đối tượng cho loại của nó và giả sử API tồn tại và thất bại tương ứng nếu không. Thứ hai là thích hợp hơn trong các ngôn ngữ gõ lỏng lẻo. Xem stackoverflow.com/a/408305/2419669 . Cách try-catchtiếp cận cũng rõ ràng hơn nhiều if (foo && foo.bar && foo.bar.baz && foo.bar.baz.qux) { ... }.
yangmillstheory

24

làm thế nào về

try {
   alert(test.level1.level2.level3)
} catch(e) {
 ...whatever

}

15
Tôi không nghĩ thử / bắt là một cách tốt để kiểm tra sự tồn tại của một đối tượng: thử / bắt có nghĩa là xử lý các trường hợp ngoại lệ, không phải là điều kiện bình thường như thử nghiệm ở đây. Tôi nghĩ (typeof foo == "không xác định") ở mỗi bước là tốt hơn - và nói chung, có thể có một số cấu trúc lại cần thiết nếu bạn đang làm việc với các thuộc tính lồng nhau sâu sắc như vậy. Ngoài ra, thử / bắt sẽ gây ra lỗi trong Fireorms (và trong bất kỳ trình duyệt nào có bật lỗi ngắt) nếu ném ngoại lệ.
Sam Dutton

Tôi bỏ phiếu về điều này, bởi vì trình duyệt sẽ kiểm tra sự tồn tại hai lần nếu bạn sử dụng các giải pháp khác. Hãy nói rằng bạn muốn gọi ắcacb = 2. Trình duyệt phải kiểm tra sự tồn tại trước khi sửa đổi giá trị (nếu không đó sẽ là lỗi bộ nhớ do HĐH bắt được).

4
Câu hỏi vẫn còn đó: witch one nhanh hơn để các trình duyệt thiết lập thử bắt hay gọi hasOwnProperty()n lần?

14
Tại sao điều này lại tồi tệ? Điều này có vẻ sạch sẽ nhất đối với tôi.
Austin cầu nguyện

Tôi sẽ nói: Nếu bạn mong đợi rằng tài sản tồn tại hơn thì có thể bọc nó thành một khối thử. Nếu nó không tồn tại thì đó là một lỗi. Nhưng nếu bạn chỉ lười biếng và đặt mã thông thường vào khối bắt cho trường hợp tài sản không tồn tại thì thử / bắt bị sử dụng sai. Ở đây một if / khác hoặc một cái gì đó tương tự được yêu cầu.
robsch

18

Câu trả lời ES6, đã kiểm tra kỹ lưỡng :)

const propExists = (obj, path) => {
    return !!path.split('.').reduce((obj, prop) => {
        return obj && obj[prop] ? obj[prop] : undefined;
    }, obj)
}

→ xem Codepen với phạm vi kiểm tra đầy đủ


Tôi đã thực hiện các thử nghiệm của bạn không thành công khi đặt giá trị của prop phẳng thành 0. Bạn phải quan tâm đến kiểu ép buộc.
nảy mầm

@germain Cái này có hiệu quả với bạn không? (Tôi so sánh rõ ràng ===cho các falsys khác nhau và thêm bài kiểm tra. Nếu bạn có ý tưởng tốt hơn, hãy cho tôi biết).
Frank Nocke

Tôi đã làm các bài kiểm tra của bạn thất bại một lần nữa khi đặt giá trị của prop phẳng false. Và sau đó bạn có thể muốn có một giá trị trong đối tượng của mình được đặt thành undefined(Tôi biết điều đó thật kỳ lạ nhưng là JS). Tôi đã tạo một giá trị sai dương được đặt thành 'Prop not Found':const hasTruthyProp = prop => prop === 'Prop not found' ? false : true const path = obj => path => path.reduce((obj, prop) => { return obj && obj.hasOwnProperty(prop) ? obj[prop] : 'Prop not found' }, obj) const myFunc = compose(hasTruthyProp, path(obj))
nảy mầm

Bạn có thể rẽ nhánh codepen của tôi (trên cùng bên phải, dễ dàng), sửa và thêm các bài kiểm tra và gửi cho tôi URL của bạn không? Cảm ơn =)
Frank Nocke

Chạy đến thư viện bên thứ 3 (rất lớn) ... có thể, nhưng không phải sở thích của tôi.
Frank Nocke

17

Bạn cũng có thể sử dụng đề xuất xích tùy chọn tc39 cùng với babel 7 - tc39-đề xuất-tùy chọn-xích

Mã sẽ trông như thế này:

  const test = test?.level1?.level2?.level3;
  if (test) alert(test);

Lưu ý rằng cú pháp này gần như chắc chắn sẽ thay đổi, vì một số thành viên TC39 có phản đối.
jhpratt ĐÁNG TIN CẬY GOFUNDME

Có thể nhưng điều này sẽ có sẵn trong một số hình thức trong thời gian và đó là điều duy nhất quan trọng .. Đó là một trong những tính năng tôi nhớ nhất trong JS.
Goran.it

11

Tôi đã thử một cách tiếp cận đệ quy:

function objHasKeys(obj, keys) {
  var next = keys.shift();
  return obj[next] && (! keys.length || objHasKeys(obj[next], keys));
}

Các ! keys.length ||cú đá ra khỏi đệ quy để nó không chạy chức năng mà không còn phím nào để kiểm tra. Các xét nghiệm:

obj = {
  path: {
    to: {
      the: {
        goodKey: "hello"
      }
    }
  }
}

console.log(objHasKeys(obj, ['path', 'to', 'the', 'goodKey'])); // true
console.log(objHasKeys(obj, ['path', 'to', 'the', 'badKey']));  // undefined

Tôi đang sử dụng nó để in chế độ xem html thân thiện của một loạt các đối tượng có khóa / giá trị không xác định, ví dụ:

var biosName = objHasKeys(myObj, 'MachineInfo:BiosInfo:Name'.split(':'))
             ? myObj.MachineInfo.BiosInfo.Name
             : 'unknown';

9

Tôi nghĩ rằng các kịch bản sau đây cho đại diện dễ đọc hơn.

khai báo một hàm:

var o = function(obj) { return obj || {};};

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

if (o(o(o(o(test).level1).level2).level3)
{

}

Tôi gọi nó là "kỹ thuật chú hề buồn" bởi vì nó đang sử dụng dấu o (


BIÊN TẬP:

đây là phiên bản dành cho TypeScript

nó cung cấp kiểm tra loại tại thời gian biên dịch (cũng như intellisense nếu bạn sử dụng một công cụ như Visual Studio)

export function o<T>(someObject: T, defaultValue: T = {} as T) : T {
    if (typeof someObject === 'undefined' || someObject === null)
        return defaultValue;
    else
        return someObject;
}

cách sử dụng là như nhau:

o(o(o(o(test).level1).level2).level3

nhưng lần này làm việc intellisense!

Ngoài ra, bạn có thể đặt giá trị mặc định:

o(o(o(o(o(test).level1).level2).level3, "none")

1
°0o <°(())))><
Daniel W.

1
Tôi thích cái này, vì nó trung thực và ném "không xác định" vào mặt bạn khi bạn không biết Objectloại của mình . +1.

1
Miễn là bạn giữ lời tuyên bố trong parens, bạn cũng có thể gọi đó là kỹ thuật chú hề hạnh phúc (o
Sventies

Cảm ơn Sventies. Tôi thích bình luận của bạn. Đó là một góc khá đẹp để nhìn từ - các điều kiện như vậy chủ yếu được sử dụng trong "ifs" và luôn được bao quanh với dấu ngoặc ngoài. Vì vậy, vâng, nó thực sự là một chú hề hạnh phúc :)))
VeganHunter

Bạn thực sự cần phải yêu dấu ngoặc đơn để thực hiện điều này ...
Bastien7

7

tạo ra một toàn cầu functionvà sử dụng trong toàn bộ dự án

thử cái này

function isExist(arg){
   try{
      return arg();
   }catch(e){
      return false;
   }
}

let obj={a:5,b:{c:5}};

console.log(isExist(()=>obj.b.c))
console.log(isExist(()=>obj.b.foo))
console.log(isExist(()=>obj.test.foo))

nếu có điều kiện

if(isExist(()=>obj.test.foo)){
   ....
}

Công trình tuyệt vời. Đơn giản và hiệu quả
gbland777

6

Một cách đơn giản là thế này:

try {
    alert(test.level1.level2.level3);
} catch(e) {
    alert("undefined");    // this is optional to put any output here
}

Các try/catchtrường hợp bắt các trường hợp khi bất kỳ đối tượng cấp cao hơn như test, test.level1, test.level1.level2 không được xác định.


6

Tôi không thấy bất kỳ ví dụ nào về việc ai đó sử dụng Proxy

Vì vậy, tôi đã đưa ra với riêng tôi. Điều tuyệt vời ở đây là bạn không phải nội suy chuỗi. Bạn thực sự có thể trả về một hàm đối tượng có khả năng chuỗi và làm một số điều kỳ diệu với nó. Bạn thậm chí có thể gọi các hàm và lấy các chỉ mục mảng để kiểm tra các đối tượng sâu

Các mã trên hoạt động tốt cho các công cụ đồng bộ. Nhưng làm thế nào bạn sẽ kiểm tra một cái gì đó không đồng bộ như cuộc gọi ajax này? Làm thế nào để bạn kiểm tra điều đó? Điều gì xảy ra nếu phản hồi không phải là json khi nó trả về lỗi 500 http?

window.fetch('https://httpbin.org/get')
.then(function(response) {
  return response.json()
})
.then(function(json) {
  console.log(json.headers['User-Agent'])
})

chắc chắn rằng bạn có thể sử dụng async / await để thoát khỏi một số cuộc gọi lại. Nhưng nếu bạn có thể làm điều đó thậm chí còn kỳ diệu hơn thì sao? một cái gì đó trông như thế này:

fetch('https://httpbin.org/get').json().headers['User-Agent']

Bạn có thể tự hỏi tất cả các lời hứa và .thenchuỗi đang ở đâu ... điều này có thể chặn tất cả những gì bạn biết ... nhưng sử dụng cùng một kỹ thuật Proxy với lời hứa bạn thực sự có thể kiểm tra đường dẫn phức tạp lồng nhau sâu sắc cho sự tồn tại của nó mà không bao giờ viết một hàm duy nhất


Nếu ai đó quan tâm, tôi đã xuất bản phiên bản async vào npm
Endless

5

Dựa trên câu trả lời này , tôi đã đưa ra hàm chung này bằng cách sử dụng ES2015nó sẽ giải quyết vấn đề

function validChain( object, ...keys ) {
    return keys.reduce( ( a, b ) => ( a || { } )[ b ], object ) !== undefined;
}

var test = {
  first: {
    second: {
        third: "This is not the key your are looking for"
    }
  }
}

if ( validChain( test, "first", "second", "third" ) ) {
    console.log( test.first.second.third );
}

1
Đây là cách tiếp cận cuối cùng của tôifunction validChain (object, path) { return path.split('.').reduce((a, b) => (a || { })[b], object) !== undefined }
James Harrington

5

Tôi đã tạo ra một chức năng nhỏ để có được các thuộc tính đối tượng lồng nhau một cách an toàn.

function getValue(object, path, fallback, fallbackOnFalsy) {
    if (!object || !path) {
        return fallback;
    }

    // Reduces object properties to the deepest property in the path argument.
    return path.split('.').reduce((object, property) => {
       if (object && typeof object !== 'string' && object.hasOwnProperty(property)) {
            // The property is found but it may be falsy.
            // If fallback is active for falsy values, the fallback is returned, otherwise the property value.
            return !object[property] && fallbackOnFalsy ? fallback : object[property];
        } else {
            // Returns the fallback if current chain link does not exist or it does not contain the property.
            return fallback;
        }
    }, object);
}

Hoặc một phiên bản đơn giản hơn nhưng hơi khó đọc:

function getValue(o, path, fb, fbFalsy) {
   if(!o || !path) return fb;
   return path.split('.').reduce((o, p) => o && typeof o !== 'string' && o.hasOwnProperty(p) ? !o[p] && fbFalsy ? fb : o[p] : fb, o);
}

Hoặc thậm chí ngắn hơn nhưng không có dự phòng trên cờ giả:

function getValue(o, path, fb) {
   if(!o || !path) return fb;
   return path.split('.').reduce((o, p) => o && typeof o !== 'string' && o.hasOwnProperty(p) ? o[p] : fb, o);
}

Tôi có bài kiểm tra với:

const obj = {
    c: {
        a: 2,
        b: {
            c: [1, 2, 3, {a: 15, b: 10}, 15]
        },
        c: undefined,
        d: null
    },
    d: ''
}

Và đây là một số thử nghiệm:

// null
console.log(getValue(obj, 'c.d', 'fallback'));

// array
console.log(getValue(obj, 'c.b.c', 'fallback'));

// array index 2
console.log(getValue(obj, 'c.b.c.2', 'fallback'));

// no index => fallback
console.log(getValue(obj, 'c.b.c.10', 'fallback'));

Để xem tất cả mã với tài liệu và các bài kiểm tra tôi đã thử, bạn có thể kiểm tra ý chính của tôi: https://gist.github.com/vsambor/3df9ad75ff3de361bbcb7b8c60beebf4#file-javascriptgetnestedvalues-js


4

Một phiên bản ngắn hơn, ES5 của câu trả lời tuyệt vời của @ CMS:

// Check the obj has the keys in the order mentioned. Used for checking JSON results.  
var checkObjHasKeys = function(obj, keys) {
  var success = true;
  keys.forEach( function(key) {
    if ( ! obj.hasOwnProperty(key)) {
      success = false;
    }
    obj = obj[key];
  })
  return success;
}

Với một bài kiểm tra tương tự:

var test = { level1:{level2:{level3:'result'}}};
utils.checkObjHasKeys(test, ['level1', 'level2', 'level3']); // true
utils.checkObjHasKeys(test, ['level1', 'level2', 'foo']); // false

Vấn đề duy nhất với điều này là nếu có nhiều cấp độ của các khóa không xác định, thì bạn sẽ có TypeError, ví dụ:checkObjHasKeys(test, ['level1', 'level2', 'asdf', 'asdf']);
JKS

1
Một phương pháp phù hợp hơn là mọi giá trị có thể được trả về trực tiếp.
RobG

Có lẽ đổi success = false;thành return false. Bạn nên bảo lãnh một khi bạn biết nó vỡ, không gì có thể tồn tại sâu hơn một khi nó không có giá trị hoặc không xác định. Điều này sẽ ngăn các lỗi trên các mục lồng nhau sâu hơn, vì rõ ràng chúng không tồn tại.
Wade

4

Tôi nghĩ rằng đây là một cải tiến nhỏ (trở thành 1-liner):

   alert( test.level1 && test.level1.level2 && test.level1.level2.level3 )

Điều này hoạt động vì toán tử && trả về toán hạng cuối cùng mà nó đã đánh giá (và nó bị đoản mạch).


4

Tôi đang tìm kiếm giá trị được trả lại nếu tài sản tồn tại, vì vậy tôi đã sửa đổi câu trả lời bằng CMS ở trên. Đây là những gì tôi nghĩ ra:

function getNestedProperty(obj, key) {
  // Get property array from key string
  var properties = key.split(".");

  // Iterate through properties, returning undefined if object is null or property doesn't exist
  for (var i = 0; i < properties.length; i++) {
    if (!obj || !obj.hasOwnProperty(properties[i])) {
      return;
    }
    obj = obj[properties[i]];
  }

  // Nested property found, so return the value
  return obj;
}


Usage:

getNestedProperty(test, "level1.level2.level3") // "level3"
getNestedProperty(test, "level1.level2.foo") // undefined


3

Câu trả lời được đưa ra bởi CMS cũng hoạt động tốt với sửa đổi sau đây đối với kiểm tra null

function checkNested(obj /*, level1, level2, ... levelN*/) 
      {
             var args = Array.prototype.slice.call(arguments),
             obj = args.shift();

            for (var i = 0; i < args.length; i++) 
            {
                if (obj == null || !obj.hasOwnProperty(args[i]) ) 
                {
                    return false;
                }
                obj = obj[args[i]];
            }
            return true;
    }

3

Các lựa chọn sau đây đã được xây dựng bắt đầu từ câu trả lời này . Cùng một cây cho cả hai:

var o = { a: { b: { c: 1 } } };

Dừng tìm kiếm khi không xác định

var u = undefined;
o.a ? o.a.b ? o.a.b.c : u : u // 1
o.x ? o.x.y ? o.x.y.z : u : u // undefined
(o = o.a) ? (o = o.b) ? o.c : u : u // 1

Đảm bảo từng cấp một

var $ = function (empty) {
    return function (node) {
        return node || empty;
    };
}({});

$($(o.a).b).c // 1
$($(o.x).y).z // undefined

3

Tôi biết câu hỏi này đã cũ, nhưng tôi muốn cung cấp một phần mở rộng bằng cách thêm câu hỏi này vào tất cả các đối tượng. Tôi biết mọi người có xu hướng nhăn mặt khi sử dụng nguyên mẫu Object cho chức năng đối tượng mở rộng, nhưng tôi không tìm thấy gì dễ dàng hơn việc làm này. Thêm vào đó, giờ đây nó được phép với phương thức Object.defineProperty .

Object.defineProperty( Object.prototype, "has", { value: function( needle ) {
    var obj = this;
    var needles = needle.split( "." );
    for( var i = 0; i<needles.length; i++ ) {
        if( !obj.hasOwnProperty(needles[i])) {
            return false;
        }
        obj = obj[needles[i]];
    }
    return true;
}});

Bây giờ, để kiểm tra bất kỳ thuộc tính nào trong bất kỳ đối tượng nào, bạn chỉ cần làm:

if( obj.has("some.deep.nested.object.somewhere") )

Đây là một jsfiddle để kiểm tra nó và đặc biệt nó bao gồm một số jQuery bị hỏng nếu bạn sửa đổi trực tiếp Object.prototype vì thuộc tính trở nên vô số. Điều này sẽ hoạt động tốt với các thư viện bên thứ 3.


3

Điều này hoạt động với tất cả các đối tượng và mảng :)

Ví dụ:

if( obj._has( "something.['deep']['under'][1][0].item" ) ) {
    //do something
}

đây là phiên bản cải tiến của câu trả lời của Brian

Tôi đã sử dụng _has làm tên thuộc tính vì nó có thể xung đột với thuộc tính hiện có (ví dụ: maps)

Object.defineProperty( Object.prototype, "_has", { value: function( needle ) {
var obj = this;
var needles = needle.split( "." );
var needles_full=[];
var needles_square;
for( var i = 0; i<needles.length; i++ ) {
    needles_square = needles[i].split( "[" );
    if(needles_square.length>1){
        for( var j = 0; j<needles_square.length; j++ ) {
            if(needles_square[j].length){
                needles_full.push(needles_square[j]);
            }
        }
    }else{
        needles_full.push(needles[i]);
    }
}
for( var i = 0; i<needles_full.length; i++ ) {
    var res = needles_full[i].match(/^((\d+)|"(.+)"|'(.+)')\]$/);
    if (res != null) {
        for (var j = 0; j < res.length; j++) {
            if (res[j] != undefined) {
                needles_full[i] = res[j];
            }
        }
    }

    if( typeof obj[needles_full[i]]=='undefined') {
        return false;
    }
    obj = obj[needles_full[i]];
}
return true;
}});

Đây là câu đố


3

Đây là ý kiến ​​của tôi - hầu hết các giải pháp này đều bỏ qua trường hợp của một mảng lồng nhau như trong:

    obj = {
        "l1":"something",
        "l2":[{k:0},{k:1}],
        "l3":{
            "subL":"hello"
        }
    }

Tôi có thể muốn kiểm tra obj.l2[0].k

Với chức năng dưới đây, bạn có thể làm deeptest('l2[0].k',obj)

Hàm sẽ trả về true nếu đối tượng tồn tại, sai khác

function deeptest(keyPath, testObj) {
    var obj;

    keyPath = keyPath.split('.')
    var cKey = keyPath.shift();

    function get(pObj, pKey) {
        var bracketStart, bracketEnd, o;

        bracketStart = pKey.indexOf("[");
        if (bracketStart > -1) { //check for nested arrays
            bracketEnd = pKey.indexOf("]");
            var arrIndex = pKey.substr(bracketStart + 1, bracketEnd - bracketStart - 1);
            pKey = pKey.substr(0, bracketStart);
			var n = pObj[pKey];
            o = n? n[arrIndex] : undefined;

        } else {
            o = pObj[pKey];
        }
        return o;
    }

    obj = get(testObj, cKey);
    while (obj && keyPath.length) {
        obj = get(obj, keyPath.shift());
    }
    return typeof(obj) !== 'undefined';
}

var obj = {
    "l1":"level1",
    "arr1":[
        {"k":0},
        {"k":1},
        {"k":2}
    ],
    "sub": {
       	"a":"letter A",
        "b":"letter B"
    }
};
console.log("l1: " + deeptest("l1",obj));
console.log("arr1[0]: " + deeptest("arr1[0]",obj));
console.log("arr1[1].k: " + deeptest("arr1[1].k",obj));
console.log("arr1[1].j: " + deeptest("arr1[1].j",obj));
console.log("arr1[3]: " + deeptest("arr1[3]",obj));
console.log("arr2: " + deeptest("arr2",obj));


3

Bây giờ chúng ta cũng có thể sử dụng reduceđể lặp qua các khóa lồng nhau:

// @params o<object>
// @params path<string> expects 'obj.prop1.prop2.prop3'
// returns: obj[path] value or 'false' if prop doesn't exist

const objPropIfExists = o => path => {
  const levels = path.split('.');
  const res = (levels.length > 0) 
    ? levels.reduce((a, c) => a[c] || 0, o)
    : o[path];
  return (!!res) ? res : false
}

const obj = {
  name: 'Name',
  sys: { country: 'AU' },
  main: { temp: '34', temp_min: '13' },
  visibility: '35%'
}

const exists = objPropIfExists(obj)('main.temp')
const doesntExist = objPropIfExists(obj)('main.temp.foo.bar.baz')

console.log(exists, doesntExist)


3

Bạn có thể làm điều này bằng cách sử dụng hàm đệ quy. Điều này sẽ hoạt động ngay cả khi bạn không biết tất cả tên khóa đối tượng lồng nhau.

function FetchKeys(obj) {
    let objKeys = [];
    let keyValues = Object.entries(obj);
    for (let i in keyValues) {
        objKeys.push(keyValues[i][0]);
        if (typeof keyValues[i][1] == "object") {
            var keys = FetchKeys(keyValues[i][1])
            objKeys = objKeys.concat(keys);
        }
    }
    return objKeys;
}

let test = { level1: { level2: { level3: "level3" } } };
let keyToCheck = "level2";
let keys = FetchKeys(test); //Will return an array of Keys

if (keys.indexOf(keyToCheck) != -1) {
    //Key Exists logic;
}
else {
    //Key Not Found logic;
}

2

Có một chức năng ở đây trên thecodablesode (safeRead) sẽ thực hiện việc này một cách an toàn ... tức là

safeRead(test, 'level1', 'level2', 'level3');

nếu bất kỳ thuộc tính nào là null hoặc không xác định, một chuỗi rỗng được trả về


Tôi rất thích phương pháp này với templating vì nó trả về một chuỗi rỗng nếu không được đặt
Lounge9

2

Dựa trên một nhận xét trước đó , đây là một phiên bản khác mà đối tượng chính không thể được xác định:

// Supposing that our property is at first.second.third.property:
var property = (((typeof first !== 'undefined' ? first : {}).second || {}).third || {}).property;

2

Tôi đã viết hàm riêng của mình có đường dẫn mong muốn và có hàm gọi lại tốt và xấu.

function checkForPathInObject(object, path, callbackGood, callbackBad){
    var pathParts = path.split(".");
    var currentObjectPath = object;

    // Test every step to see if it exists in object
    for(var i=0; i<(pathParts.length); i++){
        var currentPathPart = pathParts[i];
        if(!currentObjectPath.hasOwnProperty(pathParts[i])){
            if(callbackBad){
                callbackBad();
            }
            return false;
        } else {
            currentObjectPath = currentObjectPath[pathParts[i]];
        }
    }

    // call full path in callback
    callbackGood();
}

Sử dụng:

var testObject = {
    level1:{
        level2:{
            level3:{
            }
        }
    }
};


checkForPathInObject(testObject, "level1.level2.level3", function(){alert("good!")}, function(){alert("bad!")}); // good

checkForPathInObject(testObject, "level1.level2.level3.levelNotThere", function(){alert("good!")}, function(){alert("bad!")}); //bad

Tôi mặc dù công bằng khi cung cấp cho bạn tín dụng cho cảm hứng để điều chỉnh mã của bạn theo câu trả lời của tôi
davewoodhall

2
//Just in case is not supported or not included by your framework
//***************************************************
Array.prototype.some = function(fn, thisObj) {
  var scope = thisObj || window;
  for ( var i=0, j=this.length; i < j; ++i ) {
    if ( fn.call(scope, this[i], i, this) ) {
      return true;
    }
  }
  return false;
};
//****************************************************

function isSet (object, string) {
  if (!object) return false;
  var childs = string.split('.');
  if (childs.length > 0 ) {
    return !childs.some(function (item) {
      if (item in object) {
        object = object[item]; 
        return false;
      } else return true;
    });
  } else if (string in object) { 
    return true;
  } else return false;
}

var object = {
  data: {
    item: {
      sub_item: {
        bla: {
          here : {
            iam: true
          }
        }
      }
    }
  }
};

console.log(isSet(object,'data.item')); // true
console.log(isSet(object,'x')); // false
console.log(isSet(object,'data.sub_item')); // false
console.log(isSet(object,'data.item')); // true
console.log(isSet(object,'data.item.sub_item.bla.here.iam')); // true
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.