Làm thế nào để tránh lỗi 'không thể đọc thuộc tính của không xác định'?


118

Trong mã của tôi, tôi xử lý một mảng có một số mục nhập với nhiều đối tượng được lồng vào nhau, trong khi một số thì không. Nó trông giống như sau:

// where this array is hundreds of entries long, with a mix
// of the two examples given
var test = [{'a':{'b':{'c':"foo"}}}, {'a': "bar"}];

Điều này gây ra cho tôi vấn đề vì tôi cần phải lặp đi lặp lại mảng này đôi khi và sự không nhất quán đang ném cho tôi những lỗi như vậy:

for (i=0; i<test.length; i++) {
    // ok on i==0, but 'cannot read property of undefined' on i==1
    console.log(a.b.c);
}

Tôi biết rằng tôi có thể nói if(a.b){ console.log(a.b.c)}, nhưng điều này cực kỳ tẻ nhạt trong trường hợp có tới 5 hoặc 6 đối tượng lồng vào nhau. Có cách nào khác (dễ dàng hơn) mà tôi có thể CHỈ làm trên console.log nếu nó tồn tại, nhưng không gặp lỗi không?


3
Lỗi có thể là một ngoại lệ Javascript thông thường, vì vậy hãy thử try..catchcâu lệnh. Điều đó nói rằng, một mảng có chứa các phần tử hoàn toàn không đồng nhất trông giống như một vấn đề thiết kế đối với tôi.
millimoose

3
Nếu cấu trúc của bạn không nhất quán giữa các mục, thì có gì sai khi kiểm tra sự tồn tại? Thực sự, tôi muốn sử dụng if ("b" in a && "c" in a.b). Nó có thể là "tẻ nhạt", nhưng đó là những gì bạn nhận được cho sự mâu thuẫn ... logic thông thường.
Ian

2
Tại sao bạn truy cập các thuộc tính không tồn tại, tại sao bạn không biết các đối tượng trông như thế nào?
Bergi

9
Tôi có thể hiểu tại sao ai đó sẽ không muốn một lỗi làm hỏng mọi thứ. Bạn không phải lúc nào cũng dựa vào thuộc tính của một đối tượng để tồn tại hay không tồn tại. Nếu bạn có sẵn thứ gì đó có thể xử lý trường hợp đối tượng không đúng định dạng, thì mã của bạn sẽ hiệu quả hơn và ít mỏng manh hơn.
SSH này

3
Bạn sẽ ngạc nhiên khi thấy có bao nhiêu đối tượng / mảng đang bị thay đổi trong những tình huống thực tế đời sống
OneMoreQuestion

Câu trả lời:


120

Cập nhật :

  • Nếu bạn sử dụng JavaScript theo ECMAScript 2020 trở lên, hãy xem chuỗi tùy chọn .
  • TypeScript đã thêm hỗ trợ cho chuỗi tùy chọn trong phiên bản 3.7 .
// use it like this
obj?.a?.lot?.of?.properties

Giải pháp cho JavaScript trước ECMASCript 2020 hoặc TypeScript cũ hơn phiên bản 3.7 :

Một giải pháp nhanh chóng là sử dụng chức năng trợ giúp thử / bắt với chức năng mũi tên ES6 :

function getSafe(fn, defaultVal) {
    try {
        return fn();
    } catch (e) {
        return defaultVal;
    }
}

// use it like this
getSafe(() => obj.a.lot.of.properties);

// or add an optional default value
getSafe(() => obj.a.lot.of.properties, 'nothing');

Đoạn mã làm việc:

Xem bài viết này để biết chi tiết.


2
Tôi thích nó! điều duy nhất tôi muốn thêm là một console.warn bên trong bắt, để bạn biết về lỗi nhưng nó vẫn tiếp tục.
Rabbi Shuki Gur

Việc nắm bắt tất cả các ngoại lệ mà không ném lại là không tốt và nhìn chung việc sử dụng các ngoại lệ như một phần của quy trình thực thi dự kiến ​​cũng không tốt - mặc dù trong trường hợp này, nó được chứa khá tốt.
hugo

50

Những gì bạn đang làm tạo ra một ngoại lệ (và đúng như vậy).

Bạn luôn có thể làm

try{
   window.a.b.c
}catch(e){
   console.log("YO",e)
}

Nhưng tôi sẽ không nghĩ đến trường hợp sử dụng của bạn.

Tại sao bạn đang truy cập dữ liệu, 6 cấp độ lồng vào nhau mà bạn không quen? Trường hợp sử dụng nào chứng minh điều này?

Thông thường, bạn muốn thực sự xác thực loại đối tượng mà bạn đang xử lý.

Ngoài ra, một lưu ý nhỏ là bạn không nên sử dụng các câu lệnh như if(a.b)vì nó sẽ trả về false nếu ab là 0 hoặc thậm chí nếu nó là "0". Thay vào đó hãy kiểm tra xema.b !== undefined


1
Liên quan đến chỉnh sửa đầu tiên của bạn: Nó là hợp lý; Tôi đang xử lý các mục nhập cơ sở dữ liệu có cấu trúc JSON, như vậy các đối tượng sẽ mở rộng quy mô nhiều cấp trường (tức là. Entry.users.messages.date, v.v., trong đó không phải tất cả các trường hợp đều có dữ liệu được nhập)
Ari

"nó sẽ trả về true nếu ab là 0" - nope. typeof a.b === "undefined" && a.b!=null- không cần thiết phải làm phần thứ hai sau phần đầu tiên và sẽ có ý nghĩa hơn nếu chỉ làmif ("b" in a)
Ian

@ Ian yeah, tôi rõ ràng có ý ngược lại, nó sẽ trả về false ngay cả khi ab là "0". Bắt đẹp
Benjamin Gruenbaum

@BenjaminGruenbaum Nghe hay đấy, không chắc bạn có muốn nói vậy không. Ngoài ra, tôi nghĩ bạn muốn typeof a.b !== "undefined" && ab! = Null` - lưu ý!==
Ian

3
Nếu bạn không muốn nhàm chán ab && abc && console.log (abc) thì đây là cách duy nhất để ghi nhật ký các ẩn số một cách nhất quán.
Brian Cray

14

Nếu tôi hiểu đúng câu hỏi của bạn, bạn muốn cách an toàn nhất để xác định xem một đối tượng có chứa thuộc tính hay không.

Cách đơn giản nhất là sử dụng intoán tử .

window.a = "aString";
//window should have 'a' property
//lets test if it exists
if ("a" in window){
    //true
 }

if ("b" in window){
     //false
 }

Tất nhiên bạn có thể lồng nó sâu như bạn muốn

if ("a" in window.b.c) { }

Không chắc liệu điều này có giúp ích gì không.


9
Bạn không thể làm tổ sâu này một cách an toàn như bạn muốn. Nếu window.blà không xác định thì sao? Bạn sẽ gặp lỗi kiểu:Cannot use 'in' operator to search for 'c' in undefined
Trevor

13

Nếu bạn đang sử dụng lodash , bạn có thể sử dụng hàm "has" của chúng. Nó tương tự như "in" gốc, nhưng cho phép các đường dẫn.

var testObject = {a: {b: {c: 'walrus'}}};
if(_.has(testObject, 'a.b.c')) {
  //Safely access your walrus here
}

2
Tốt nhất, chúng tôi có thể sử dụng _.get()với mặc định cho dễ đọc:_.get(object, 'a.b.c', 'default');
Ifnot

12

Thử cái này. Nếu a.bkhông được xác định, nó sẽ rời khỏi ifcâu lệnh mà không có bất kỳ ngoại lệ nào.

if (a.b && a.b.c) {
  console.log(a.b.c);
}

5

Đây là một vấn đề phổ biến khi làm việc với đối tượng json sâu hoặc phức tạp, vì vậy tôi cố gắng tránh thử / bắt hoặc nhúng nhiều kiểm tra sẽ làm cho mã không thể đọc được, tôi thường sử dụng đoạn mã nhỏ này trong tất cả quy trình của mình để thực hiện công việc.

/* ex: getProperty(myObj,'aze.xyz',0) // return myObj.aze.xyz safely
 * accepts array for property names: 
 *     getProperty(myObj,['aze','xyz'],{value: null}) 
 */
function getProperty(obj, props, defaultValue) {
    var res, isvoid = function(x){return typeof x === "undefined" || x === null;}
    if(!isvoid(obj)){
        if(isvoid(props)) props = [];
        if(typeof props  === "string") props = props.trim().split(".");
        if(props.constructor === Array){
            res = props.length>1 ? getProperty(obj[props.shift()],props,defaultValue) : obj[props[0]];
        }
    }
    return typeof res === "undefined" ? defaultValue: res;
}

5

Nếu bạn có lodash, bạn có thể sử dụng .getphương pháp của nó

_.get(a, 'b.c.d.e')

hoặc cung cấp cho nó một giá trị mặc định

_.get(a, 'b.c.d.e', default)

4

Tôi sử dụng không an toàn một cách tôn giáo. Nó kiểm tra từng cấp độ đối với đối tượng của bạn cho đến khi nó nhận được giá trị mà bạn yêu cầu hoặc nó trả về "không xác định". Nhưng không bao giờ sai sót.


2
đó là tương tự như lodash_.get
Filype

Chúc mừng! Vẫn hữu ích nếu bạn không cần các tính năng khác của lodash.
martinedwards


3

Tôi thích câu trả lời của Cao Shouguang, nhưng tôi không thích truyền một hàm dưới dạng tham số vào hàm getSafe mỗi lần tôi thực hiện cuộc gọi. Tôi đã sửa đổi hàm getSafe để chấp nhận các tham số đơn giản và ES5 thuần túy.

/**
* Safely get object properties.    
* @param {*} prop The property of the object to retrieve
* @param {*} defaultVal The value returned if the property value does not exist
* @returns If property of object exists it is returned, 
*          else the default value is returned.
* @example
* var myObj = {a : {b : 'c'} };
* var value;
* 
* value = getSafe(myObj.a.b,'No Value'); //returns c 
* value = getSafe(myObj.a.x,'No Value'); //returns 'No Value'
* 
* if (getSafe(myObj.a.x, false)){ 
*   console.log('Found')
* } else {
*  console.log('Not Found') 
* }; //logs 'Not Found'
* 
* if(value = getSafe(myObj.a.b, false)){
*  console.log('New Value is', value); //logs 'New Value is c'
* }
*/
function getSafe(prop, defaultVal) {
  return function(fn, defaultVal) {
    try {
      if (fn() === undefined) {
        return defaultVal;
      } else {
        return fn();
      }
    } catch (e) {
      return defaultVal;
    }
  }(function() {return prop}, defaultVal);
}

Nó không thực sự hoạt động với getSafe(myObj.x.c). Đã thử phiên bản mới nhất của Chrome và Firefox.
Rickard Elimää

2

Trong câu trả lời của str, giá trị 'không xác định' sẽ được trả về thay vì giá trị mặc định đã đặt nếu thuộc tính là không xác định. Điều này đôi khi có thể gây ra lỗi. Phần sau sẽ đảm bảo defaultVal sẽ luôn được trả về khi thuộc tính hoặc đối tượng không được xác định.

const temp = {};
console.log(getSafe(()=>temp.prop, '0'));

function getSafe(fn, defaultVal) {
    try {
        if (fn() === undefined) {
            return defaultVal
        } else {
            return fn();
        }

    } catch (e) {
        return defaultVal;
    }
}

Phiên bản mã cải tiến của Hardy Le Roux không hoạt động với let myObj = {} getSafe (() => myObj.ab, "nice"), trong khi mã của tôi hoạt động. Có ai giải thích tại sao không?
Cao Shouguang

2

Lodash có một getphương thức cho phép mặc định làm tham số thứ ba tùy chọn, như hình dưới đây:

const myObject = {
  has: 'some',
  missing: {
    vars: true
  }
}
const path = 'missing.const.value';
const myValue = _.get(myObject, path, 'default');
console.log(myValue) // prints out default, which is specified above
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.11/lodash.js"></script>


2

Hãy tưởng tượng rằng chúng ta muốn áp dụng một loạt các hàm cho xnếu và chỉ khi xkhông phải là null:

if (x !== null) x = a(x);
if (x !== null) x = b(x);
if (x !== null) x = c(x);

Bây giờ, hãy nói rằng chúng ta cần làm như vậy để y:

if (y !== null) y = a(y);
if (y !== null) y = b(y);
if (y !== null) y = c(y);

Và tương tự với z:

if (z !== null) z = a(z);
if (z !== null) z = b(z);
if (z !== null) z = c(z);

Như bạn có thể thấy nếu không có sự trừu tượng thích hợp, chúng ta sẽ kết thúc việc sao chép mã nhiều lần. Một sự trừu tượng như vậy đã tồn tại: đơn nguyên Có thể .

Đơn nguyên Có thể giữ cả giá trị và ngữ cảnh tính toán:

  1. Đơn nguyên giữ giá trị an toàn và áp dụng các chức năng cho nó.
  2. Bối cảnh tính toán là một kiểm tra rỗng trước khi áp dụng một hàm.

Một triển khai ngây thơ sẽ trông như thế này:

⚠️ Việc thực hiện này chỉ mang tính chất minh họa! Đây không phải là cách nên làm và sai ở nhiều cấp độ. Tuy nhiên, điều này sẽ cung cấp cho bạn một ý tưởng tốt hơn về những gì tôi đang nói về.

Như bạn có thể thấy, không gì có thể phá vỡ:

  1. Chúng tôi áp dụng một loạt các chức năng cho giá trị của chúng tôi
  2. Nếu tại bất kỳ thời điểm nào, giá trị trở thành null (hoặc không xác định) thì chúng ta sẽ không áp dụng bất kỳ hàm nào nữa.

const abc = obj =>
  Maybe
    .of(obj)
    .map(o => o.a)
    .map(o => o.b)
    .map(o => o.c)
    .value;

const values = [
  {},
  {a: {}},
  {a: {b: {}}},
  {a: {b: {c: 42}}}
];

console.log(

  values.map(abc)

);
<script>
function Maybe(x) {
  this.value = x; //-> container for our value
}

Maybe.of = x => new Maybe(x);

Maybe.prototype.map = function (fn) {
  if (this.value == null) { //-> computational context
    return this;
  }
  return Maybe.of(fn(this.value));
};
</script>


Phụ lục 1

Tôi không thể giải thích monads là gì vì đây không phải là mục đích của bài đăng này và có những người ngoài kia giỏi hơn tôi. Tuy nhiên, như Eric Elliot đã nói trong bài đăng trên blog lịch sử JavaScript Monads Made Simple :

Bất kể trình độ kỹ năng của bạn hay sự hiểu biết về lý thuyết danh mục, việc sử dụng monads giúp mã của bạn dễ làm việc hơn. Việc không tận dụng được các monads có thể khiến mã của bạn khó làm việc hơn (ví dụ: địa ngục gọi lại, các nhánh có điều kiện lồng nhau, nhiều chi tiết hơn).


Phụ lục 2

Đây là cách tôi giải quyết vấn đề của bạn bằng cách sử dụng đơn nguyên Có thể từ

const prop = key => obj => Maybe.fromNull(obj[key]);

const abc = obj =>
  Maybe
    .fromNull(obj)
    .flatMap(prop('a'))
    .flatMap(prop('b'))
    .flatMap(prop('c'))
    .orSome('🌯')
    
const values = [
  {},
  {a: {}},
  {a: {b: {}}},
  {a: {b: {c: 42}}}
];

console.log(

  values.map(abc)

);
<script src="https://www.unpkg.com/monet@0.9.0/dist/monet.js"></script>
<script>const {Maybe} = Monet;</script>


0

Tôi đã trả lời điều này trước đây và tình cờ thực hiện một kiểm tra tương tự ngày hôm nay. Đơn giản hóa để kiểm tra xem thuộc tính dấu chấm lồng nhau có tồn tại hay không. Bạn có thể sửa đổi điều này để trả về giá trị hoặc một số giá trị mặc định để hoàn thành mục tiêu của bạn.

function containsProperty(instance, propertyName) {
    // make an array of properties to walk through because propertyName can be nested
    // ex "test.test2.test.test"
    let walkArr = propertyName.indexOf('.') > 0 ? propertyName.split('.') : [propertyName];

    // walk the tree - if any property does not exist then return false
    for (let treeDepth = 0, maxDepth = walkArr.length; treeDepth < maxDepth; treeDepth++) {

        // property does not exist
        if (!Object.prototype.hasOwnProperty.call(instance, walkArr[treeDepth])) {
            return false;
        }

        // does it exist - reassign the leaf
        instance = instance[walkArr[treeDepth]];

    }

    // default
    return true;

}

Trong câu hỏi của bạn, bạn có thể làm điều gì đó như:

let test = [{'a':{'b':{'c':"foo"}}}, {'a': "bar"}];
containsProperty(test[0], 'a.b.c');

0

Tôi thường sử dụng như thế này:

 var x = object.any ? object.any.a : 'def';

0

Bạn có thể tránh gặp lỗi bằng cách đưa ra giá trị mặc định trước khi nhận thuộc tính

var test = [{'a':{'b':{'c':"foo"}}}, {'a': "bar"}];

for (i=0; i<test.length; i++) {
    const obj = test[i]
    // No error, just undefined, which is ok
    console.log(((obj.a || {}).b || {}).c);
}

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

const entries = [{id: 1, name: 'Scarllet'}]
// Giving a default name when is empty
const name = (entries.find(v => v.id === 100) || []).name || 'no-name'
console.log(name)

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.