ES2020 可选链:a?.().b 和 a()?.b 和 a?.()?.b 有什么区别

ES2020 optional chaining: what's the difference between a?.().b and a()?.b and a?.()?.b

假设我们有这个对象:

let obj = {};

这些表达式的具体作用是什么?

obj.a?.().b - 如果 obj.anullundefined 那么表达式是 undefined 否则表达式的计算结果是 obj.a().b.

obj.a()?.b - 如果 obj.a()nullundefined 那么表达式是 undefined 否则表达式的计算结果是 obj.a().b

obj.a?.()?.b 如果 obj.aobj.a()nullundefined 则表达式为 undefined 否则表达式计算为 obj.a().b.

阅读更多关于可选链的信息here

obj.a?.().b

  1. 直接取消引用 obj

  2. 以空安全方式取消引用 obj.a - 如果 属性 不存在,它将在此时停止,它是 undefinednull。如果发生这种情况,计算表达式的结果将是 undefined.

  3. 直接执行那个值。

  4. 收到 return 结果并继续。

  5. 直接从结果中得到属性b

const tryIt = obj => {
  console.log("------start------");

  console.log("trying with", obj );

  try {
    console.log( "result", obj.a?.().b );
  } catch (e) {
    console.error("problem", e.message);
  }

  console.log("-------end-------");
}

tryIt( null );                 // ERROR
tryIt( {} );                   // undefined
tryIt( { a: undefined } );     // undefined
tryIt( { a: null } );          // undefined
tryIt( { a: false } );         // ERROR
tryIt( { a: "hello" } );       // ERROR
tryIt( { a: function() {} } ); // ERROR
tryIt( {                       // ERROR
  a: function() {
    return null; 
  }
});
tryIt( {                       // 42
  a: function() {
    return { b: 42 };
  }
});

obj.a()?.b

  1. 直接取消引用 obj

  2. 直接取消引用 obj.a

  3. 直接执行那个值。

  4. 以空安全方式处理值 - 如果 return 值为 null 或 [=16=,它将在此时停止].如果发生这种情况,计算表达式的结果将是 undefined.

  5. 直接从结果中得到属性b

const tryIt = obj => {
  console.log("------start------");

  console.log("trying with", obj );

  try {
    console.log( "result", obj.a()?.b );
  } catch (e) {
    console.error("problem", e.message);
  }

  console.log("-------end-------");
}


tryIt( null );                 // ERROR
tryIt( {} );                   // ERROR
tryIt( { a: undefined } );     // ERROR
tryIt( { a: null } );          // ERROR
tryIt( { a: false } );         // ERROR
tryIt( { a: "hello" } );       // ERROR
tryIt( { a: function() {} } ); // undefined
tryIt( {                       // undefined
  a: function() {
    return null; 
  }
});
tryIt( {                       // 42
  a: function() {
    return { b: 42 };
  }
});

obj.a?.()?.b

  1. 直接取消引用 obj

  2. 以空安全方式取消引用 obj.a - 如果 属性 不存在,它将在此时停止,它是 undefinednull。如果发生这种情况,计算表达式的结果将是 undefined.

  3. 直接执行那个值。

  4. 以空安全方式处理值 - 如果 return 值为 null 或 [=16=,它将在此时停止].如果发生这种情况,计算表达式的结果将是 undefined.

  5. 直接从结果中得到属性b

const tryIt = obj => {
  console.log("------start------");

  console.log("trying with", obj );

  try {
    console.log( "result", obj.a?.()?.b );
  } catch (e) {
    console.error("problem", e.message);
  }

  console.log("-------end-------");
}


tryIt( null );                 // ERROR
tryIt( {} );                   // undefined
tryIt( { a: undefined } );     // undefined
tryIt( { a: null } );          // undefined
tryIt( { a: false } );         // ERROR
tryIt( { a: "hello" } );       // ERROR
tryIt( { a: function() {} } ); // undefined
tryIt( {                       // undefined
  a: function() {
    return null; 
  }
});
tryIt( {                       // 42
  a: function() {
    return { b: 42 };
  }
});

optional chaining operator可用于三个位置:

  1. 代替 属性 访问的点运算符 (obj?.a)
  2. 紧接 属性 访问的括号语法之前 (obj?.['a'])
  3. 紧接函数调用之前 (obj.a?.())

在属性访问位置,如果属性值为空值(即nullundefined) , 然后它立即短路整个表达式, returns undefined.

在函数调用位置,如果函数为nullish,则立即短路整个表达式,returns undefined(从而避免 "undefined is not a function" 异常)。

所以:

  • obj.a?.().b 将 return undefined 如果 obj.a 为空。

  • 如果 obj.a().b 的结果为空,
  • obj.a()?.b 将 return undefined

  • obj.a?.()?.b 将 return undefined 如果 a 为空,或者如果 obj.a() 的结果为空。

TL;TR

检查表达式的 non-undefinednot-null 值和 ? 并且在任何时候,如果发现值是其中之一,链就会断开并 returns undefined.

需要注意的一个非常重要的一点是它只保护 对抗 undefinednull 值而不是对抗 falsy 值,即它仍然会传递其他虚假值,如 NaN 或 "".

详细说明

让我们从引用开始 MDN

The optional chaining operator provides a way to simplify accessing values through connected objects when it's possible that a reference or function may be undefined or null.

考虑这样一个对象:

 var obj = {
       a: {
           b: 1
       },
     }

现在验证 objobj 的属性 a 然后 a 的 属性 b 不是 undefinednull,你可能需要做这样的事情:

 obj && obj.a && obj.a.b

可选链接为您提供了替代方法。你可以简单地这样做:

obj?.a?.b

现在,假设 a 恰好是一个函数,它 return 是一个具有 属性 b 的对象,其值 1。像这样:

 var obj = {
           a: function{
               return {b:1}
            },
       }

那么,对于深度嵌套的 b,您现在将如何验证 obj 及其字段?那么,你可以这样做:

obj && obj.a && typeof obj.a === 'function' && obj.a() && obj.a().b

或者你可以简单地做

obj?.a()?.b

这只是意味着检查 obj 是否为非未定义和非空,(如果是)然后检查 obj?.a() 是否相同,(如果是则)执行方法 a() 的对象。如果执行后方法 return 有一个值(不是 undefinednull),则从中获取 属性 b 的值。

在此 检查 期间的任何时候,如果发现值是 undefinednull 只需断开链并 return undefined

但是,如果 a 恰好不是在前面的表达式中用 typeof obj.a === 'function' 检查的函数,这将失败。 另外,请注意,此时如果 obj.a() 恰好出现在 return 上,比如“baz”,它将被执行为 "baz".b 这会给你 undefined.

有了这些知识,我们可以轻松破译这个表达式:

   obj.a?.().b

检查obj.a是否为non-undefinednot-null。如果是,则执行 obj.a() (注意,如果 obj.a 不是一个函数,而是 returns 说,数字 5,它将抛出一个错误)

如果 obj.a() 碰巧执行成功,那么 .b 的返回值将被检查。

现在剩下这个

obj.a?.()?.b

嗯,这也像我们之前的执行一样执行:

如果是 not-nullnon-undefined,请检查 obj.a。如果是,执行obj.a。如果那也是non-nullnon-undefined,执行obj.a()等等。

如果在检查时的任何时候发现值是 nullundefined,链就会中断。

需要注意的非常重要的一点是,我们的运算符 ? 仅防范未定义和 null 而不是其他 falsy 值。

考虑一下

var a = undefined
var myVal = a?.details.b;
alert(a) //will return undefined

var a = ""
var myVal = a?.details.b;
alert(a) //Our guard fails us here and simply throws an error