发布于 2026-01-06 1 阅读
0

JavaScript DEV 的全球展示挑战赛(由 Mux 呈现):展示你的项目!

在 JavaScript 中是可选的(空安全)。

由 Mux 赞助的 DEV 全球展示挑战赛:展示你的项目!

昨天我偶然看到了StackOverflow 上的这个问题,它让我开始思考如何nullundefinedJavaScript 中处理这个问题。

简要背景

什么是“ undefined?”? undefined这是一个原始值,它仅用于已声明的变量、不存在的属性或函数参数。

null?” null是另一个表示无值的原始值。

那么,当我们执行以下操作时会发生什么呢?


let obj;

console.log(obj.someProp);

Enter fullscreen mode Exit fullscreen mode

我们收到以下错误

TypeError:无法读取未定义对象的属性“someProp”。

同样的情况也发生在……null

空值检查

那么,我们该如何避免这种情况呢?幸运的是,JavaScript 有short-circuit求值机制,这意味着为了避免之前的问题,TypeError我们可以这样写。


let obj;

console.log(obj && obj.someProp); // Prints undefined

Enter fullscreen mode Exit fullscreen mode

但如果我们想更深入地了解情况呢?比如…… obj.prop1.prop2.prop3?我们最终会做很多检查,例如:


console.log( obj && obj.prop1 && obj.prop1.prop2 && obj.prop1.prop2.prop3 );

Enter fullscreen mode Exit fullscreen mode

感觉很讨厌,不是吗?

如果我们想在链式调用中存在“undefined或”运算符时打印默认值呢null?那么表达式就会变得更大:


const evaluation = obj && obj.prop1 && obj.prop1.prop2 && obj.prop1.prop2.prop3;

console.log( evaluation != null ? evaluation : "SomeDefaultValue" );

Enter fullscreen mode Exit fullscreen mode

其他语言是如何实现的?

这个问题并非 JavaScript 独有,大多数编程语言都存在这个问题,所以让我们来看看如何在一些编程语言中进行空值检查。

Java

Java 中有以下OptionalAPI:


SomeClass object;

Optional.ofNullable(object)
    .map(obj -> obj.prop1)
    .map(obj -> obj.prop2)
    .map(obj -> obj.prop3)
    .orElse("SomeDefaultValue");

Enter fullscreen mode Exit fullscreen mode

Kotlin

在 Kotlin(另一种 JVM 语言)中,有Elvis ( ?:) 和安全调用( ?.) 运算符。


val object: SomeClass?

object?.prop1?.prop2?.prop3 ?: "SomeDefaultValue";

Enter fullscreen mode Exit fullscreen mode

C#

最后,在 C# 中,我们还有空条件运算符( ?.) 和空合并运算符( ??)。


SomeClass object;

object?.prop1?.prop2?.prop3 ?? "SomeDefaultValue";

Enter fullscreen mode Exit fullscreen mode

JS选项

看完这些之后,我就在想,有没有办法避免写那么多 JavaScript 代码呢?于是我开始尝试用正则表达式编写一个函数,让我可以安全地访问对象的属性。


function optionalAccess(obj, path, def) {
  const propNames = path.replace(/\]|\)/, "").split(/\.|\[|\(/);

  return propNames.reduce((acc, prop) => acc[prop] || def, obj);
}

const obj = {
  items: [{ hello: "Hello" }]
};

console.log(optionalAccess(obj, "items[0].hello", "def")); // Prints Hello
console.log(optionalAccess(obj, "items[0].he", "def")); // Prints def

Enter fullscreen mode Exit fullscreen mode

之后,我发现了lodash._get具有相同签名的[此处应填写签名]:

_.get(object, path, [defaultValue])

但说实话,我不太喜欢字符串路径,所以我开始寻找避免使用它们的方法,然后我想到了一种使用代理的解决方案:


// Here is where the magic happens
function optional(obj, evalFunc, def) {

  // Our proxy handler
  const handler = {
    // Intercept all property access
    get: function(target, prop, receiver) {
      const res = Reflect.get(...arguments);

      // If our response is an object then wrap it in a proxy else just return
      return typeof res === "object" ? proxify(res) : res != null ? res : def;
    }
  };

  const proxify = target => {
    return new Proxy(target, handler);
  };

  // Call function with our proxified object
  return evalFunc(proxify(obj, handler));
}

const obj = {
  items: [{ hello: "Hello" }]
};

console.log(optional(obj, target => target.items[0].hello, "def")); // => Hello
console.log(optional(obj, target => target.items[0].hell, { a: 1 })); // => { a: 1 }

Enter fullscreen mode Exit fullscreen mode

未来

目前, TC39有一项提案,允许我们执行以下操作:


obj?.arrayProp?[0]?.someProp?.someFunc?.();

Enter fullscreen mode Exit fullscreen mode

看起来很棒,对吧?不过,这个方案目前还处于第一阶段,这意味着我们可能还需要一段时间才能看到它用 JavaScript 实现。尽管如此,目前有一个 Babel插件可以让我们使用这种语法。

终曲

null空安全的概念已经存在一段时间了,而且我敢肯定它是编程中最令人讨厌的概念之一。然而,还是有办法实现空安全。以下是我的一些看法,欢迎大家留言讨论,或者分享你们的其他方案。

附:这里有个小概要

文章来源:https://dev.to/pichardoj/optional-null-safe-in-javascript-1b7k