有效避免 Cannot read properties of null/undefined 报错

发布时间:2024-01-08 11:24 分类:HTML·CSS·JS

前言

TypeError: Cannot read properties of null (reading 'xxxx')
TypeError: Cannot read properties of undefined (reading 'name')

这是 JavaScript 典型的运行时错误,不同于编译时错误,这往往是在程序正式运行后才会出现。所以我们很难提前预知,不论是新手小白,还是技术大牛,在整个开发生涯中,都不可避免的与其打交道。

那么,知其原理,学习对策,方能避而远之。

原理分析

JavaScript 中的类型分为两大类,两大类又各自包含多个子类。第一大类是原始类型,包含 NumberStringBooleanNullUndefinedSymbolBigInt。第二大类是对象类型,包含 ObjectArrayFunctionDateRegExp 等多个内置对象类型。

原始类型本身是不可变的简单值,没有属性和方法。但如果尝试访问一个原始类型的属性或调用方法时,JavaScript 引擎会在后台临时创建一个对应的包装对象,使它看起来像是有属性或方法。这就是为什么 StringNumberBoolean 等原始类型存在.toString() 方法的原因。即便是访问原始类型不存在的属性时,也可以返回 undefined,不至于抛出错误。例如:

const str = 'Hello Word1';
console.log(str.name); // undefined

既然 nullundefined 在 JavaScript 中也被定义为原始类型,为何访问不存在属性时却抛出错误?因为 JS 引擎没有给他们提供包装对象。来看一下它们定义:

  • null:表示“空值”或“空对象引用”,是一种明确赋值的状态,表示某个变量没有值。
  • undefined:表示“未定义”,是变量未被赋值的默认状态。

对于这样定义的类型,访问它们的属性是没有意义的,这是一种发生在编程中的错误行为,所以自然会抛出错误。

知识拓展
为什么 typeof null === "object" ?这是 JavaScript 设计上的一个历史遗留问题。在最初的实现中,JavaScript 使用低位二进制表示变量类型。对象的类型标志位是 000,而 null 恰好被表示为 000,因此被误识别为对象类型。为了向后兼容,这个问题被保留下来,没有修复。

错误原因及解决方案

出现 TypeError: Cannot read properties of null (reading 'xxxx') 报错的原因有很多,下面介绍几种常见的情况以及相对应的解决方案。

1. 变量未正确初始化

原因: 声明一个变量但是没有赋值时,其值默认为 undefined 。

let obj;
console.log(obj.name); // TypeError: Cannot read properties of undefined (reading 'name')

解决方案

// 1. 定义变量时设置初始值
let obj1 = {}; // 设置初始值为 {} 而不是 null
console.log(obj1.name); // undefined

// 2. 使用变量前进行逻辑判断
let obj2;

if (obj2) {
    console.log(obj2.name); // undefined
}

2. 异步数据加载未完成

原因: fetch 和 response.json() 是异步操作,需要等待它们完成之后 data 才能被赋值。

let data = null;

fetch('/api/data').then(response => {
    data = response.json();
});

console.log(data.name); // TypeError: Cannot read properties of null (reading 'name')

解决方案

// 1. 在 .then() 方法中执行后续操作
fetch('/api/data')
    .then(response => response.json()) // 等待 JSON 数据解析完成
    .then(data => {
        console.log(data.name);        // 这里访问是安全的
    })
    .catch(error => {
        console.error('Error:', error);
    });

// 1. 使用 async/await
async function fetchData() {
    try {
        const response = await fetch('/api/data'); // 等待请求完成
        const data = await response.json();        // 等待 JSON 解析完成
        console.log(data.name);                    // 安全访问数据
    } catch (error) {
        console.error('Error:', error);            // 捕获异常
    }
}

fetchData();

3. 对象嵌套属性缺失

原因: 尝试访问嵌套对象的属性,但某个中间属性是 null 或 undefined。

let user = { profile: null };
console.log(user.profile.name);  // TypeError: Cannot read properties of null (reading 'name')

解决方案

// 使用可选链操作符 ?.
let user = { profile: null };
console.log(user.profile?.name); // undefined

知识拓展
可选链操作符 ?. 是 JavaScript ES2020 中引入的一种语法,用于安全地访问嵌套对象的属性或调用方法,而无需显式检查中间对象是否为 null 或 undefined。当使用 ?. 访问一个属性或方法时,如果该操作链中任何一部分为 null 或 undefined,整个表达式会短路并返回 undefined,而不会抛出错误。

4. 函数返回值为 null 或 undefined

原因: 某些函数可能返回 null 或 undefined,而没有正确处理返回值。

function getUser() {
    return null;
}
console.log(getUser().name);  // TypeError: Cannot read properties of null (reading 'name')

解决方案

function getUser() {
    return null;
}

// 1. 使用 || 为返回值设置默认值
const user = getUser() || { name: 'Default Name' };
console.log(user.name); // 'Default Name'

// 1. 使用 ?? 为返回值设置默认值
const user = getUser() ?? { name: 'Default Name' };
console.log(user.name); // 'Default Name'

知识拓展
||(逻辑或运算符)与 ??(空值合并运算符)都是从左向右判断,并返回第一个真值,若没有真值,则返回最右侧的值(即便是假值); || 与 ?? 的区别是,|| 判断所有假值(false、''、0、NaN、null、underined),而 ?? 只会将 null 和 undefined 视为假值;

5. 数组索引越界

原因: 访问数组中不存在的索引位置。

let arr = [1, 2, 3];
console.log(arr[3].name); // 报错:Cannot read properties of undefined

解决方案

// 1. 检查索引范围
if (arr[3]) {
    console.log(arr[3].name);
}

总结

在 JavaScript 开发中,Cannot read properties of null/undefined 是一种常见但可以有效预防的错误。 通过以下策略,可以大大减少此类问题的发生:

  • 变量初始化:声明变量时赋初始值,避免 undefined。
  • 异步数据处理:正确处理异步数据加载,使用 then()async/await
  • 可选链操作符 ?.:安全访问嵌套属性,避免中间节点为 null 或 undefined 时出错。
  • 默认值 ||??:为可能为 null/undefined 的值设置默认值,确保逻辑安全。
  • 范围检查:访问数组索引或对象属性前,确保目标存在。

养成良好的编码习惯,确保每个变量、属性或返回值都有合理的初始状态。善用现代 JavaScript 特性(如可选链、空值合并操作符等) 提高代码的安全性和可读性。便可从容应对这类错误!