13161216443

您所在位置: 首頁> 學習課程> java培訓 | 解決 JavaScript 中處理 null 和 undefined 的麻煩事

java培訓 | 解決 JavaScript 中處理 null 和 undefined 的麻煩事

發布百知教育 來源:學習課程 2019-11-13

許多 JavaScript 開發人員正在為怎么處理可選值頭痛。有什么好辦法來最大程度地減少由值(可能為 null、undefined或在運行時未初始化)引起的錯誤?

在某些情況下,一些語言具有內置功能。在某些靜態類型的語言中,你可以說 null undefined 是非法值,并且讓你的編程語言在編譯時拋出 TypeError,但是即使在那些語言中,也無法防止 null 輸入在運行時流入程序。

為了更好地處理這類問題,我們需要了解這些值的來源。以下是一些最常見的來源:

  • 用戶輸入

  • 數據庫/網絡記錄

  • 未初始化狀態

  • 函數什么也不會返回


User Input

用戶輸入

在處理用戶輸入時,對這些輸入進行驗證是第一道也是最好的防線。我經常依靠模式驗證器來完成這項工作。例如,檢查react-jsonschema-form【https://rjsf-team.github.io/react-jsonschema-form/】。

從流水記錄輸入

我總是從網絡、數據庫或用戶輸入的流水記錄中獲得的輸入。例如我可以用 redux action creators【https://medium.com/javascript-scene/10-tips-for-better-redux-architecture-69250425af44】 處理undefined 值來合并用戶記錄:

1const setUser = ({ name = 'Anonymous', avatar = 'anon.png' } = {}) => ({
2  type: setUser.type,
3  payload: {
4    name,
5    avatar
6  }
7});
8setUser.type = 'userReducer/setUser';

有時,你需要根據數據的當前狀態顯示不同的內容。如果在初始化所有數據之前顯示頁面,則可能會遇到這種情況。例如當你向用戶顯示資金余額時,可能會在加載數據之前意外地顯示余額為 $ 0,這會讓用戶感到不安。你可以創建自定義數據類型,這些數據類型根據當前狀態生成不同的輸出:

 1const createBalance = ({
2  // default state
3  state = 'uninitialized',
4  value = createBalance.empty
5} = {}) => ({
6  __proto__: {
7    uninitialized() => '--',
8    initialized() => value,
9    format () {
10      return this[this.getState()](value);
11    },
12    getState() => state,
13    setvalue => {
14      const test = Number(value);
15      assert(!Number.isNaN(test), `setBalance Invalid value: ${ value }`);
16      return createBalance({
17        state'initialized',
18        value
19      });
20    }
21  }
22});
23createBalance.empty = '0';const setBalance = value => createBalance().set(value);const emptyBalanceForDisplay = createBalance()
24  .format();
25console.log(emptyBalanceForDisplay); // '--'const balanceForDisplay = setBalance('25')
26  .format(balance);
27console.log(balanceForDisplay); // '25'// Uncomment these calls to see the error cases:
28// setBalance('foo'); // Error: setBalance Invalid value: foo// Error: createBalance Invalid state: THIS IS NOT VALID
29// createBalance({ state: 'THIS IS NOT VALID', value: '0' });

上面的代碼是一個狀態機,不會顯示無效狀態。當首次創建余額時,它將被設置為uninitialized 狀態。如果你在狀態 uninitialized 時嘗試顯示余額,則始終會得到一個占位符值(“--”)。

要更改這個值,你必須通過調用 .set 方法或在 createBalance 工廠中定義的 setBalance來顯式的設置一個值。

狀態本身是 encapsulated【https://medium.com/javascript-scene/encapsulation-in-javascript-26be60e325b4】,以保護其免受外界干擾,且可以確保其他函數無法捕獲它并將其設置為無效狀態。

注意:你可能想知道為什么我要用字符串而不是數字來舉例,那是因為用大數字符串來表示貨幣類型具有十進制精度,可以避免舍入錯誤,并準確地表示加密貨幣交易的值,這樣可以得到任意有效的十進制精度。

如果你使用 Redux 或 Redux 架構,則可以用 Redux-DSM【https://github.com/ericelliott/redux-dsm】 聲明狀態機。

避免創建 `null` 和 `undefined` 值

在你自己的函數中,可以避免一開始就創建 null  undefined 值。我想到了很多內置于 JavaScript 的方法。見下文。

避免 null

我從來沒有在 JavaScript 中顯式地創建過 null 值,因為我從來沒有真正看到過它的意義。

從 2015 年以來,JavaScript 開始支持默認值,當你不提供相關參數或屬性的值時,這些默認值就會被填寫。這些默認設置不適用于 null 值。根據我的經驗,這通常是一個錯誤。為了避免這種陷阱,請不要在 JavaScript 中使用 null。

如果你希望對未初始化的值或空值使用特殊情況,則狀態機是更好的選擇。

新的 JavaScript 功能

有幾個功能可以幫助你處理 nul  undefined 值。在撰寫本文時,這兩個都是第 3 階段的建議。也許將來你就可以使用它們了。

在撰寫本文時,可選鏈接是第 3 階段的建議。它是這樣的:

1const foo = {};
2// console.log(foo.bar.baz); // throws error
3console.log(foo.bar?.baz) // undefined

空位合并運算符

“空位合并運算符”也是要添加到規范中的第3階段建議,它基本上是“后備值運算符”的一種奇特方法。如果左側的值為 undefined  null,則其求值為右側的值。它是這樣的:

1let baz;
2console.log(baz); // undefined
3console.log(baz ?? 'default baz');
4// default baz// Combine with optional chaining:
5console.log(foo.bar?.baz ?? 'default baz');
6// default baz

如果未來還沒有到來,則需要安裝 @babel/plugin-proposal-optional-chaining @babel/plugin-proposal-nullish-coalescing-operator.。

異步與 Promise

如果某個函數可能沒有返回值,那么最好將其包裝在 Either 中。在函數式編程中,Either monad 是一種特殊的抽象數據類型,它允許你附加兩個不同的代碼路徑:成功路徑或失敗路徑。 JavaScript 有稱為 Promise 的內置異步Either monad-ish 數據類型【https://medium.com/javascript-scene/javascript-monads-made-simple-7856be57bfe8】。你可以用它對未定義的值進行聲明式錯誤分支:

1const exists = x => x != null;const ifExists = value => exists(value) ?
2  Promise.resolve(value) :
3  Promise.reject(`Invalid value: ${ value }`);ifExists(null).then(log).catch(log); // Invalid value: null
4ifExists('hello').then(log).catch(log); // hello

你可以根據需要編寫一個同步版本,但我把它留給你做練習。如果你對 functors【https://medium.com/javascript-scene/functors-categories-61e031bac53f】 和  monads【https://medium.com/javascript-scene/javascript-monads-made-simple-7856be57bfe8】 比較熟悉,那么過程將變得更加容易;如果這聽起來令人生畏,也不用擔心,只不過是使用 promise。它們是內置的,并且在大多數情況下都可以正常工作。

Maybe 數組

數組實現一個 map 方法,該方法采用一個應用于每個元素數組的函數。如果數組為空,則永遠不會調用該函數。換句話說,JavaScript 中的數組可以填補 Haskell 等語言中 Maybe 的角色。

什么是Maybe?

Maybe 是一種特殊的抽象數據類型,它封裝了一個可選值。數據類型有兩種形式:

  • Just — 包含一個值

  • Nothing — 沒有值

其核心思想是這樣的:

 1const log = x => console.log(x);
2const exists = x => x != null;const Just = value => ({
3  mapf => Just(f(value)),
4});const Nothing = () => ({
5  map() => Nothing(),
6});const Maybe = value => exists(value) ?
7  Just(value) :
8  Nothing();const empty = undefined;
9Maybe(empty).map(log); // does not log
10Maybe('Maybe Foo').map(log); // logs "Maybe Foo"

上面僅是演示這個概念的例子。你可以圍繞 Maybe 建立一個有用函數的完整庫去實現其他操作,如 flatMap  flat(在編寫多個 Maybe 返回函數時,避免使用 Just(Just(value)))。但是 JavaScript 已經有了一種數據類型,該數據類型可以直接實現這些功能,它就是數組。

如果你要創建一個可能會也可能不會產生結果的函數(尤其是可能有多個結果),則下面是一個很好的例子。

1const log = x => console.log(x);
2const exists = x => x != null;const arr = [1,2,3];
3const find = (p, list) => [list.find(p)].filter(exists);
4find(x => x > 3, arr).map(log); // does not log anything
5find(x => x < 3, arr).map(log); // logs 1

我發現不在空列表上調用 map 對于避免 null  undefined 值非常有用,但是請記住,如果數組中包含 null  和 undefined 值,它將調用函數處理這些值,因此,如果你的函數可能會產生 null  undefined,則需要將其從返回的數組中過濾掉。這可能會改變集合的長度。

在 Haskell 中,有一個函數maybe(類似 map)將一個函數應用于一個值。但是該值是可選的,并封裝在 Maybe 中。我們可以用 JavaScript 的 Array 數據類型做同樣的事:

 1// maybe = b => (a => b) => [a] => b
2const maybe = (fallback, f = () => {}) => arr =>
3  arr.map(f)[0] || fallback;// turn a value (or null/undefined) into a maybeArray
4const toMaybeArray = value => [value].filter(exists);// maybe multiply the contents of an array by 2,
5// default to 0 if the array is empty
6const maybeDouble = maybe(0, x => x * 2);const emptyArray = toMaybeArray(null);
7const maybe2 = toMaybeArray(2);// logs: "maybeDouble with fallback:  0"
8console.log('maybeDouble with fallback: ', maybeDouble(emptyArray));
9// logs: "maybeDouble(maybe2):  4"
10console.log('maybeDouble(maybe2): ', maybeDouble(maybe2));

maybe 會使用一個后備值,然后是一個映射到 may 數組上的函數,然后是一個 may 數組(一個數組包含一個值,或者什么都不包含),然后返回將該函數應用于數組內容的結果,或者返回數組為空時的值。

為了方便起見,我還定義了一個 toMaybeArray 函數,并添加了一個 maybe 函數來進行演示。

如果你想在生產環境代碼中執行類似的操作,我已經創建了一個經過單元測試的開源庫,可以使它變得更容易,它的名字是 Maybearray【https://github.com/ericelliott/maybearray】。 Maybearray 與其他 JavaScript Maybe 庫相比的優勢在于,它使用原生 JavaScript 數組去表示值,因此你不必對其進行任何特殊處理或進行任何轉換處理。當你在調試中遇到 Maybe 數組時,不必問“這是什么奇怪的類型?!”,它只是一個值數組或一個空數組,你已經看到過一百萬遍了。


作者:Eric Elliott

翻譯:瘋狂的技術宅

來源:medium


java培訓班:http://www.akpsimsu.com/java2019


上一篇:校企合作 | 遼寧工程技術大學領導抵京參觀百知教育

下一篇:應屆生去公司找個Java程序員的職位需要什么技能?

相關推薦

www.akpsimsu.com

有位老師想和您聊一聊

關閉

立即申請