JS中null、undefined与空字符串的非空判断区别

作者:青柠

时间:2026-11-02

浏览:639

深入解析JavaScript中null、undefined与空字符串三者在语义、类型机制及非空判断中的核心差异,通过实际代码与场景对比,系统讲解表单验证、接口入参与配置项默认值的正确判断方式。

文章目录
在 JavaScript 开发中,判断一个变量是否“非空”是最基础但也最容易引发隐蔽 Bug 的操作。核心区别在于:undefined 表示变量尚未赋值或属性不存在,null 表示主动赋予的“空对象占位”,而空字符串 "" 则是长度为 0 的合法字符串实体。虽然三者在隐式布尔转换中都会被判定为假值(falsy),但如果直接使用 !val 粗暴做非空判断,会连带误伤有效数值 0、布尔值 false 以及仅包含空格的输入。理解三者的本质差异并针对业务场景选择精准的判断方式,是编写健壮前端与 Node.js 代码的关键。
JS中null、undefined与空字符串的非空判断区别
图示:null、undefined 与空字符串的基本信息与语义卡片

一、三者的类型本质与语义差异

要想彻底厘清非空判断的边界,首先必须从 ECMAScript 规范的定义出发,明确这三者在类型系统和运行时内存中的真实定位。

1. undefined:系统层面的“未定义”

undefined 是 JavaScript 的原始类型之一,仅包含一个固定值 undefined。它通常由运行环境自动产生,表示“未被赋值”或“属性缺失”。以下常见场景都会产生 undefined:

  • 声明了变量但未赋予初始值:let user;
  • 访问对象上不存在的属性:const obj = {}; console.log(obj.name);
  • 函数执行后没有显式 return 语句;
  • 函数调用时未传入对应的形参。

使用 typeof undefined 得到的结果是 "undefined"。在 JSON 序列化时(JSON.stringify),包含 undefined 的对象属性会被直接忽略丢弃,而在数组中则会被转换为 null。

2. null:程序层面的“空对象占位”

null 同样是一个原始值,用来表示“无对象”或“空引用”。不同于系统自动产生的 undefined,null 通常是由开发者主动赋值的,用来明确表达“该位置将来会有一个对象,但当前处于空闲或解构状态”。例如在原型链终点 Object.prototype.__proto__ === null,或者手动释放 DOM 节点与大对象引用时 element = null。

值得特别注意的是,运行 typeof null 会返回 "object"。这是 JavaScript 最初版本留下的著名设计缺陷:底层用 32 位单元存储变量值与类型标签,而对象的类型标签为 000,null 对应的机器码全为 0,导致其被错误地标记为对象类型。在 JSON 序列化时,null 会被完整保留为 null。

3. 空字符串 "":合法的零长度文本实体

空字符串 ""(或 ''、``)是纯正的 string 原始类型。与前两者“没有具体值”的抽象状态不同,空字符串在内存中是一个真实分配的字符串实体,其长度属性 "".length === 0。它完全具备字符串原型的所有方法(如 "".trim()、"".slice()),绝不会像 null 或 undefined 那样因为访问属性而抛出 TypeError: Cannot read properties of undefined/null。

JS中null、undefined与空字符串的非空判断区别
图1:null、undefined 与空字符串的本质与内存表现对比

二、常见非空判断方式的执行逻辑与陷阱

在实际编码中,很多开发者倾向于用一行代码“通杀”所有空值,却往往忽视了每种判定表达式背后的过滤边界。下面逐一分析几种最常见的非空判断写法。

1. 隐式布尔取反:if (!val)

if (!val) 是出现频率最高但也最危险的写法。它的执行机理是先将 val 转换为布尔值,再进行逻辑非操作。JavaScript 中的假值(falsy values)不仅包括 undefined、null 和 "",还包括:

  • 数值 0、-0、0n(BigInt 零);
  • 特殊数值 NaN;
  • 布尔值 false。
// 隐患示例 1:误判数字 0
function setScore(score) {
  if (!score) {
    // 当 score 为 0 时,!score 为 true,导致合法分数 0 被重置为默认值 60!
    score = 60;
  }
  return score;
}

// 隐患示例 2:误判布尔值 false
function toggleFeature(enabled) {
  if (!enabled) {
    // 当主动传入 false 关闭功能时,会被误判为“未传参”而重新开启!
    enabled = true;
  }
  return enabled;
}

// 隐患示例 3:无法拦截纯空格字符串
const input = "   ";
if (!input) {
  // input 是包含空格的非空字符串,!input 为 false,直接绕过非空校验!
  console.log("输入为空");
} else {
  console.log("输入有效"); // 错误输出:纯空格被当作有效值放行
}
避坑重点:只有当你百分之百确定当前变量不可能取合法数值 0 或布尔值 false,并且不需要排查纯空格时,才可以使用 if (!val);否则严禁滥用。

2. 松散双等号比较:val == null 与 val != null

根据 ECMAScript 规范的抽象相等比较算法(Abstract Equality Comparison),null == undefined 的结果为 true,且它们与其他任何类型的值(包括 0、false、"")进行 == 比较时结果均为 false。

因此,val != null 是一种极其高效的惯用法,它等价于:val !== null && val !== undefined。

// 使用 val != null 过滤缺失值
function processValue(val) {
  if (val != null) {
    // 此处已精准排除 null 和 undefined
    // 有效保留了 ""、0、false!
    console.log("有效存在的值:", val);
  }
}

processValue(undefined); // 被过滤 (不输出)
processValue(null);      // 被过滤 (不输出)
processValue("");        // 输出: 有效存在的值: ""
processValue(0);         // 输出: 有效存在的值: 0
processValue(false);     // 输出: 有效存在的值: false

3. 空值合并操作符:val ?? defaultValue

ES2020 引入的空值合并操作符(Nullish Coalescing Operator,??)是语言层面对 val != null 的直接语法级支持。当且仅当左侧操作数为 null 或 undefined 时,?? 才会返回右侧的默认值。

// 对比 || 与 ??
const config = {
  pageSize: 0,
  keyword: "",
  showAll: false
};

// 错误做法:使用 ||,0、""、false 全被误覆盖
const sizeA = config.pageSize || 10;     // 结果为 10 (错误:用户指定的 0 被覆盖)
const wordA = config.keyword || "默认";  // 结果为 "默认" (错误:用户清空的搜索词被覆盖)
const showA = config.showAll || true;    // 结果为 true (错误:设为 false 被覆盖)

// 正确做法:使用 ??,仅在未提供或为 null 时回退
const sizeB = config.pageSize ?? 10;     // 结果为 0 (正确保留)
const wordB = config.keyword ?? "默认";  // 结果为 "" (正确保留)
const showB = config.showAll ?? true;    // 结果为 false (正确保留)
JS中null、undefined与空字符串的非空判断区别
图2:常见非空判断方式在不同输入下的过滤边界对比

三、业务场景中的非空判断推导与最佳实现

脱离业务场景谈非空判断是没有意义的。不同业务环节对“空”的定义完全不同:表单输入关注文本有效性,接口通信关注字段存在性,而系统配置则关注缺省回退。

JS中null、undefined与空字符串的非空判断区别
图3:非空判断业务推导与决策流程图

场景一:表单文本输入校验(过滤空串与纯空格)

在用户输入框(如用户名、手机号、评论内容)中,用户不输入任何内容时,前端框架通常会绑定初始空串 "";用户若误触空格键则会产生 " "。此时业务层面的“非空”,指的是必须包含去除首尾空白后的有效可见字符。

/**
 * 校验表单字符串是否包含有效非空白内容
 * @param {unknown} value 待检测输入
 * @returns {boolean} 是否为非空白有效字符串
 */
function isNonBlankString(value) {
  // 1. 确保必须是字符串类型,防止调用 .trim() 发生运行时异常
  if (typeof value !== "string") {
    return false;
  }
  // 2. 去除首尾连续的空格、制表符、换行符后判断长度
  return value.trim().length > 0;
}

// 运行测试用例
console.log(isNonBlankString(undefined));   // false (安全拦截)
console.log(isNonBlankString(null));        // false (安全拦截)
console.log(isNonBlankString(""));          // false (长度为0拦截)
console.log(isNonBlankString("   \t\n ")); // false (纯空白字符拦截)
console.log(isNonBlankString("hello"));     // true  (有效输入放行)
console.log(isNonBlankString(" a "));       // true  (包含可见文字放行)

场景二:接口参数清洗与 Payload 提交(保留 0 与 false)

在向后端提交查询参数或保存接口时,通常需要剔除未输入的无意义字段(null、undefined、""),但必须严格保留有效的数字 0(如状态码 status: 0、分页索引 page: 0)和布尔值 false。

/**
 * 清洗对象中无意义的空字段,保留合法 0 与 false
 * @param {Record} params 待清洗参数对象
 * @returns {Record} 清洗后的参数
 */
function sanitizeParams(params) {
  const result = {};

  for (const [key, value] of Object.entries(params)) {
    // 判定条件:既不是 null/undefined,也不是空字符串
    if (value != null && value !== "") {
      result[key] = value;
    }
  }

  return result;
}

// 示例数据
const rawQuery = {
  keyword: "",          // 空字符串 -> 需剔除
  category: null,       // null -> 需剔除
  tag: undefined,       // undefined -> 需剔除
  status: 0,            // 数字 0 -> 必须保留
  isDeleted: false,     // 布尔 false -> 必须保留
  page: 1               // 正常数值 -> 必须保留
};

console.log(sanitizeParams(rawQuery));
// 实际输出: { status: 0, isDeleted: false, page: 1 }

场景三:深层嵌套对象属性安全访问与空值兜底

当数据来源于不可控的第三方接口或异步响应时,嵌套结构可能在任意层级中断。结合可选链(Optional Chaining,?.)与空值合并(??),可以优雅且安全地读取并判断深层字段。

const response = {
  data: {
    user: {
      name: "李工",
      settings: null // 显式为空
    }
  }
};

// 安全读取深层属性:如果中间节点为 null/undefined,立即短路返回 undefined,再由 ?? 提供默认值
const theme = response?.data?.user?.settings?.theme ?? "light";
console.log("应用主题:", theme); // 输出: "light"

// 避免因访问 null 属性崩溃
const customAvatar = response?.data?.user?.avatar?.trim?.() ?? "default-avatar.png";
console.log("用户头像:", customAvatar); // 输出: "default-avatar.png"
JS中null、undefined与空字符串的非空判断区别
图4:JavaScript 非空判断场景选型速查与避坑总结

四、规范化非空工具函数封装

为了避免团队协作中不同成员写出风格各异且容易踩坑的判断逻辑,在生产级工程中,建议封装语义清晰、职责分明的工具函数。

/**
 * 常用非空判断工具集
 */
export const Validation = {
  /**
   * 判断是否为 null 或 undefined (Nullish)
   */
  isNil(value) {
    return value == null;
  },

  /**
   * 判断是否不是 null 且不是 undefined
   */
  isNotNil(value) {
    return value != null;
  },

  /**
   * 判断是否为空字符串(支持是否修剪首尾空格)
   * @param {unknown} value 待测值
   * @param {boolean} [trim=true] 是否过滤空格
   */
  isEmptyString(value, trim = true) {
    if (typeof value !== "string") return false;
    return trim ? value.trim().length === 0 : value.length === 0;
  },

  /**
   * 严格业务有值判断:排除 null、undefined 与空字符串,放行 0、false、有效对象
   */
  isValidValue(value) {
    if (value == null) return false;
    if (typeof value === "string") return value.trim().length > 0;
    if (Number.isNaN(value)) return false;
    return true;
  }
};

五、全维度对比速查表与核心记忆口诀

判断目标 推荐代码模式 被判定为空的值 被放行(视为有效)的值 核心优势与适用场景
仅排除缺失值 val != null 或 val ?? def null, undefined "", " ", 0, false, [], {} 保留数字零与空串,适合接口可选参数和默认值回退
排除缺失与空串 val != null && val !== "" null, undefined, "" 0, false, " ", [1] 精准剔除无意义空文本,同时安全保留数值与布尔假值
严格文本有效性 typeof val === "string" && val.trim() !== "" null, undefined, "", " ", 所有非字符串 包含可见字符的字符串 防止用户仅输入空格绕过前端表单必填校验
粗略假值过滤 Boolean(val) 或 if (val) null, undefined, "", 0, false, NaN 非零数值、非空字符串、对象、数组 仅用于明确不涉及 0 和 false 的简单分支流控
核心记忆原则:
  1. 查缺失用双等:判断变量是否未赋值或对象是否为空指针,牢记 val == null 一行通杀。
  2. 设兜底用双问号:配置项与函数参数设置默认值,优先使用 val ?? defaultValue,彻底告别 || 误伤数值 0 的顽疾。
  3. 验输入必调 trim:用户填写的表单字符串,必须在确保 typeof === 'string' 的前提下调用 .trim() 检查长度。
  4. 弃用盲目隐式取反:在处理不确定类型的业务数据时,拒绝使用 if (!val),始终根据期望的真实数据类型显式校验。

实用工具推荐

电子包浆

绿化、压缩、噪点和虚拟水印,自由调节做旧程度。

使用 ›
图片压缩

选择画质和输出格式,预览压缩前后的大小。

使用 ›
相关文章