JS `Optional Chaining (?.)`:安全访问深层嵌套属性与方法,避免 `TypeError`

嘿,各位代码界的探险家们,欢迎来到今天的JS魔法屋!今天我们要聊聊一个能让你在深渊般的JS对象里安全穿梭,避免被TypeError恶龙咬伤的秘密武器——可选链式调用 (?.)

第一章:TypeError恶龙的传说

在开始我们的探险之前,先来认识一下这位让我们闻风丧胆的TypeError恶龙。 想象一下,你有这样一个嵌套很深的对象:

const user = {
  profile: {
    address: {
      street: 'Main Street',
      number: 123
    }
  }
};

现在,你想获取用户的城市信息,但是,如果用户压根就没填写地址信息呢?你会怎么做? 传统的JS写法可能是这样:

let city;
if (user && user.profile && user.profile.address) {
  city = user.profile.address.city;
} else {
  city = undefined;
}

console.log(city); // undefined,如果用户没有地址信息

看起来似乎没什么问题,但如果嵌套层级更深呢?比如,你需要访问user.profile.address.location.coordinates.latitude, 难道你要写一堆if语句来判断每一层是否存在吗?这简直就是一场噩梦!

// 噩梦般的代码
let latitude;
if (user && user.profile && user.profile.address && user.profile.address.location && user.profile.address.location.coordinates) {
  latitude = user.profile.address.location.coordinates.latitude;
} else {
  latitude = undefined;
}

这就是TypeError恶龙的可怕之处。当尝试访问undefinednull的属性时,它就会跳出来,给你一个致命的TypeError: Cannot read property '...' of undefined

第二章:可选链式调用 (?.):屠龙宝刀

别怕!现在,我们有了屠龙宝刀——可选链式调用 (?.)。 它可以让你安全地访问深层嵌套的属性,而不用担心TypeError恶龙的突然袭击。

(?.) 就像一个小心翼翼的探险家,它会一层一层地探索你的对象,如果发现任何一层是nullundefined,它就会立即停止探索,并返回undefined,而不会抛出错误。

让我们用 (?.) 来改写上面的代码:

const user = {
  profile: {
    address: {
      street: 'Main Street',
      number: 123
    }
  }
};

const city = user?.profile?.address?.city; // 使用可选链式调用
console.log(city); // undefined,如果用户没有地址信息

const latitude = user?.profile?.address?.location?.coordinates?.latitude;
console.log(latitude); // undefined,即使中间的属性不存在,也不会报错

看!代码瞬间变得简洁优雅,而且安全多了!再也不用写一堆 if 语句来判断属性是否存在了。

第三章:(?.) 的用法详解

(?.) 的用法非常简单,只需要在可能为nullundefined的属性前面加上 ?. 即可。

  • 访问属性:

    const street = user?.profile?.address?.street; // 安全访问 street 属性
  • 调用方法:

    (?.) 也可以用来安全地调用方法。如果方法不存在,它会返回undefined,而不会抛出错误。

    const result = user?.profile?.getAddress?.(); // 安全调用 getAddress 方法,如果方法不存在,result 为 undefined

    注意: (?.) 只能用于方法调用,不能用于构造函数调用。例如,new user?.profile?.getAddress?.() 是无效的。

  • 与空值合并运算符 (??) 配合使用:

    (?.) 经常与空值合并运算符 (??) 配合使用,以便在属性不存在时提供一个默认值。

    const city = user?.profile?.address?.city ?? 'Unknown City'; // 如果 city 不存在,则使用默认值 'Unknown City'
    console.log(city);

    ?? 运算符只有在左侧的值为 nullundefined 时才会返回右侧的值。

  • 与短路运算符 (&&) 比较:

    (?.) 类似于短路运算符 (&&),但它们之间有一些重要的区别。

    • && 运算符在左侧的值为 falsy 值(例如,0''nullundefinedNaNfalse)时会短路。
    • (?.) 只在左侧的值为 nullundefined 时才会短路。

    例如:

    const obj = { value: 0 };
    const result1 = obj.value && obj.value.toString(); // result1 为 0 (falsy 值导致短路)
    const result2 = obj.value?.toString(); // result2 为 '0' (0 不是 null 或 undefined)

第四章:(?.) 的适用场景

(?.) 在以下场景中特别有用:

  • 处理来自 API 的数据:

    API 返回的数据结构可能不完整或不一致,使用 (?.) 可以安全地访问这些数据,避免程序崩溃。

    fetch('/api/user')
      .then(response => response.json())
      .then(user => {
        const username = user?.profile?.name ?? 'Guest'; // 安全获取用户名
        console.log(`Welcome, ${username}!`);
      });
  • 处理用户输入的数据:

    用户输入的数据可能不完整或格式错误,使用 (?.) 可以安全地处理这些数据。

    const age = parseInt(document.getElementById('age').value);
    const retirementAge = age + (user?.retirement?.age ?? 65); // 安全计算退休年龄
    console.log(`You can retire at age ${retirementAge}.`);
  • 处理第三方库返回的数据:

    第三方库可能返回不完整或不一致的数据,使用 (?.) 可以安全地访问这些数据。

第五章:(?.) 的限制

虽然 (?.) 非常强大,但它也有一些限制:

  • 不能用于赋值操作:

    (?.) 不能用于赋值操作。例如,user?.profile?.address?.city = 'New York' 是无效的。

  • 不能用于 delete 操作:

    (?.) 不能用于 delete 操作。例如,delete user?.profile?.address?.city 是无效的。

  • 只能用于属性访问和方法调用:

    (?.) 只能用于属性访问和方法调用,不能用于其他操作。例如,user?.[index] 是无效的,应该使用 user?.[index] (如果 user 是数组)。

第六章:(?.) 的兼容性

(?.) 是 ES2020 引入的特性,因此需要在支持 ES2020 的环境中才能使用。 大部分现代浏览器都支持,可以通过 Babel 等工具进行转译,使其在旧版本浏览器中也能使用。

第七章:实战演练

让我们通过几个实际的例子来巩固一下 (?.) 的用法。

例子 1:安全获取用户头像 URL

const user = {
  profile: {
    avatar: {
      small: 'small_avatar.jpg',
      large: 'large_avatar.jpg'
    }
  }
};

const avatarURL = user?.profile?.avatar?.large ?? 'default_avatar.jpg';
console.log(avatarURL); // large_avatar.jpg

const anotherUser = {};
const anotherAvatarURL = anotherUser?.profile?.avatar?.large ?? 'default_avatar.jpg';
console.log(anotherAvatarURL); // default_avatar.jpg

例子 2:安全调用回调函数

function fetchData(callback) {
  // 模拟异步操作
  setTimeout(() => {
    const data = {
      name: 'John Doe',
      age: 30
    };
    callback?.(data); // 安全调用回调函数
  }, 1000);
}

fetchData(data => {
  console.log(`Name: ${data.name}, Age: ${data.age}`);
});

fetchData(null); // 不会报错,因为使用了可选链式调用

例子 3:处理 API 返回的嵌套数据

假设 API 返回以下数据:

{
  "data": {
    "products": [
      {
        "id": 1,
        "name": "Product 1",
        "price": 10
      },
      {
        "id": 2,
        "name": "Product 2"
      }
    ]
  }
}

可以使用以下代码安全地访问产品价格:

fetch('/api/products')
  .then(response => response.json())
  .then(data => {
    const products = data?.data?.products;
    if (products) {
      products.forEach(product => {
        const price = product?.price ?? 'Price not available';
        console.log(`${product.name}: ${price}`);
      });
    }
  });

第八章:总结

(?.) 是一个非常实用的 JS 特性,可以让你更安全、更优雅地访问深层嵌套的属性和方法。 它可以避免 TypeError 恶龙的袭击,让你的代码更加健壮。

让我们用一张表格来总结一下 (?.) 的关键点:

特性 描述
作用 安全地访问深层嵌套的属性和方法,避免 TypeError
语法 object?.propertyobject?.method()
返回值 如果属性或方法存在,则返回相应的值;如果任何一层为 nullundefined,则返回 undefined
适用场景 处理 API 数据、用户输入数据、第三方库数据等,需要安全访问可能不存在的属性或方法的场景
限制 不能用于赋值操作、delete 操作,只能用于属性访问和方法调用
兼容性 ES2020,需要支持 ES2020 的环境或使用 Babel 等工具进行转译
?? 配合 可以与空值合并运算符 (??) 配合使用,提供默认值
&& 比较 (?.) 只在值为 nullundefined 时短路,而 && 在值为 falsy 值时短路

好了,今天的JS魔法屋就到这里了。希望大家掌握了 (?.) 这把屠龙宝刀,在代码的海洋里自由驰骋,不再害怕 TypeError 恶龙! 记住,代码的优雅和安全同样重要。 下次再见!

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注