返回博客

Vue 表格列显示 NaN 的真相:Number(undefined).toFixed() 的隐患

某天打开一张后台数据报表,金额、数量等数值列整齐地显示着 NaN。页面没报错,网络请求正常,数据也确实返回了——只是有几行的某些字段在接口响应里干脆没出现。

这是一个静默失败的前端 bug,只要写过 Number(row.xxx).toFixed(2) 就可能踩。

现象

Element UI 或类似封装的表格组件里,数值列 formatter 写成:

formatter: function(row) {
  return Number(row.totalAmount).toFixed(2)
}

或 slot-scope 模板里:

<template slot-scope="scope">
  {{ Number(scope.row.price).toFixed(2) }}
</template>

当 API 返回的某行数据里 totalAmount 字段不存在时,row.totalAmount 的值是 undefined,页面上对应单元格就会渲染出 NaN

根因

JavaScript 里有一个反直觉的细节:

Number(null)      // => 0       ← null 是安全的
Number(undefined) // => NaN     ← undefined 直接炸
Number("")        // => 0       ← 空字符串也安全

nullNumber() 转成 0undefined 却变成 NaN。而 NaN.toFixed(2) 不抛异常,它安静地返回字符串 "NaN" 并显示在表格里。

触发路径有很多种:

  • API 只有部分行返回某个字段(分组汇总场景)
  • 前端新增了一列,但后端还没上线对应字段
  • 接口出错只返回了部分数据
  • 从一个旧 API 切换到新 API,字段名或结构发生了变化
坑:null 安全但 undefined 不安全

Number(null) === 0,而 Number(undefined) === NaN

当你用 Number(row.field) 时,如果 row.field 是对象里不存在的属性,取到的是 undefined,直接触发 NaN。NaN.toFixed(2) 不报错,静默返回字符串 "NaN"——这是它如此难以察觉的原因。

正解

formatter 函数中(推荐写法)

// 简单场景:|| 0 兜底
formatter: function(row) {
  return Number(row.totalAmount || 0).toFixed(2)
}
 
// 严格场景:需要区分「真正的 0」和「字段缺失」
formatter: function(row) {
  const val = row.totalAmount
  return val != null ? Number(val).toFixed(2) : '0.00'
}

slot-scope 模板中

<template slot-scope="scope">
  {{ Number(scope.row.price || 0).toFixed(2) }}
</template>

computed 汇总行中

// BAD — 只要有一条数据 amount 是 undefined,整个 reduce 就变 NaN
totalAmount() {
  return this.data.reduce((sum, item) => sum + Number(item.amount), 0).toFixed(2)
}
 
// GOOD
totalAmount() {
  return this.data.reduce((sum, item) => sum + Number(item.amount || 0), 0).toFixed(2)
}

选哪种兜底方式?

| 场景 | \|\| 0 | != null ? ... : '0.00' | \|\| '-' | |------|---------|--------------------------|-----------| | 金额 / 数量 | 够用 | 更严谨(保住真实 0) | 不适用 | | 百分比 | 够用 | 更严谨 | 不适用 | | 可选指标(无数据应显示横杠) | 不适用 | 不适用 | 适用 |

正解:在 Number() 之前消灭 undefined

最省力的修法:Number(val || 0).toFixed(2)

需要严格区分「零」和「缺失」时,用 val != null ? Number(val).toFixed(2) : '0.00'——!= null 同时过滤 nullundefined,且不误杀数字 0

快速审计存量代码

# 找出所有用了 .toFixed() 但没有兜底的调用
grep -rn '\.toFixed(' src/ | grep -v '|| 0' | grep -v '!= null'

每条输出都是潜在的 NaN 风险点,逐一补兜底。

一句话外卖

Number(undefined)NaN 不是 0,任何把接口字段直接喂进 Number().toFixed() 的写法都要加 || 0!= null 保险丝——尤其在字段可能缺失的列表接口里。