避免取值時出現Cannot read property 'xx' of undefined

本文來自個人博客,歡迎你們去GitHub上star個人博客node

咱們在取值特別是鏈式取值的時候,經常會遇到Cannot read property 'xx' of undefined的錯誤,如何避免這種狀況的發生呢?這裏有幾種方法以供參考git

使用成熟的庫方法

這是最簡單的一種手段:只用引入lodash,使用_.get方法;或者引入Ramda,使用R.path方法,咱們就能規避出現上述錯誤的風險es6

儘管這種方法十分奏效且方便,但我仍是但願你能看完其餘方法github

巧用&&和||

咱們知道,在JavaScript中,使用&&或者||操做符,最後返回的值不必定是boolean類型的,好比下面這個例子:數組

console.log(undefined && "a"); //undefined
console.log("a" && "b"); //b
console.log(undefined || "a"); //a
console.log("a" || "b"); //a

&&:若是第一項是falsy(虛值,Boolean上下文中已認定可轉換爲‘假‘的值),則返回第一項,不然返回第二項瀏覽器

||:若是第一項是falsy,返回第二項,不然返回第一項安全

咱們能夠利用這種規則進行取值babel

咱們先擬一個數據app

const artcle = {
    authorInfo: {
        author: "Bowen"
    },
    artcleInfo: {
        title: "title",
        timeInfo: {
            publishTime: "today"
        }
    }
};

接下來利用&&和||進行安全取值:函數

console.log(artcle.authorInfo && artcle.authorInfo.author); //Bowen
console.log(artcle.timeInfo && artcle.timeInfo.publishTime); //undefined
console.log(artcle.artcleInfo &&
        artcle.artcleInfo.timeInfo &&
        artcle.artcleInfo.timeInfo.publishTime
); //today

console.log((artcle.authorInfo || {}).author); //Bowen
console.log((artcle.timeInfo || {}).publishTime); //undefined
console.log(((artcle.artcleInfo || {}).timeInfo || {}).publishTime); //today

不難看出,這兩種方法都不算優雅,只適用短鏈式取值,一旦嵌套過深,使用&&須要寫一長段代碼,而使用||須要嵌套不少括號

利用解構賦值的默認值

咱們能夠利用es6的解構賦值,給屬性一個默認值,避免出現錯誤。以上文的artcle數據爲例,以下:

const { authorInfo: { author } = {} } = artcle;
console.log(author); //Bowen

上面這麼作會暴露不少變量出來,咱們能夠簡單地封裝一個函數,以下:

const getAuthor = ({ authorInfo: { author } = {} } = {}) => author;
console.log(getAuthor(artcle)); //Bowen

這樣作不會將變量暴露出來,同時getAuthor函數也能複用,優雅多了

利用try catch

既然在取值的過程當中會出現錯誤,那咱們天然能夠利用try catch提早將錯誤捕獲:

let author, publishTime;
try {
    author = artcle.authorInfo.author;
} catch (error) {
    author = null;
}
try {
    publishTime = artcle.timeInfo.publishTime;
} catch (error) {
    publishTime = null;
}
console.log(author); //Bowen
console.log(publishTime); //null

這個方法很差的地方在於:咱們沒法在一個try catch語句裏進行屢次取值,由於只要有任一錯誤,就會進入catch語句中去

咱們能夠寫一個通用函數優化這一流程:

const getValue = (fn, defaultVaule) => {
    try {
        return fn();
    } catch (error) {
        return defaultVaule;
    }
};
const author = getValue(() => artcle.authorInfo.author);
const publishTime = getValue(() => artcle.timeInfo.publishTime);
console.log(author); //Bowen
console.log(publishTime); //undefined

利用proxy

這是我在網上看到的一個十分有意思的寫法,利用了es6中的proxy完成的:

const pointer = function(obj, path = []) {
    return new Proxy(function() {}, {
        get: function(target, key) {
            return pointer(obj, path.concat(key));
        },
        apply: function(target, object, args) {
            let value = obj;
            for (let i = 0; i < path.length; i++) {
                if (value == null) {
                    break;
                }
                value = value[path[i]];
            }
            if (value === undefined) {
                value = args[0];
            }
            return value;
        }
    });
};
const proxyArtcle = pointer(artcle);
console.log(proxyArtcle.authorInfo.author()); //Bowen
console.log(proxyArtcle.publishTime()); //undefined

原理比較簡單,咱們能夠看到,pointer方法返回的是一個以空函數爲代理對象的Proxy實例,而在每次取值的時候會將key保存下來,以proxyArtcle.authorInfo.author爲例,它其實等價於pointer(artcle, ["authorInfo", "author"])。因爲是以空函數爲代理對象,咱們能夠將執行它,觸發apply。apply中會遍歷path數組依次取值,若是發現沒法繼續取值則break,跳出循環。

若是你尚未學習proxy,能夠花幾分鐘瞭解一下:proxy

這種方法在我看來已經是比較優雅的解決方法,但因爲proxy對瀏覽器和node版本有所限制,且不可能有polyfill,真正應用起來須要考慮太多

optional chaining

這是一個新特性,尚在提案階段,具體能夠看tc39/proposal-optional-chaining,不過已經有babel可使用了:babel-plugin-proposal-optional-chaining

咱們能夠像下面同樣使用這個特性:

console.log(artcle?.authorInfo?.author); //Bowen
console.log(artcle?.timeInfo?.publishTime) //undefined

這種方法已接近完美,咱們能夠期待它的真正落實

相關文章
相關標籤/搜索