DEBUG.WIKI
野草云;香港 200Mbps 峰值;不限月流量 · BGP VPS;查看套餐 →
雨云;给项目一台云服务器
晚安云;香港 CN2 ¥15/月

Day.js objectSupport 遇到 null 天数,报读取 constructor 失败

objectSupport 的日期解析有空值防护,add、subtract、set 包装却可能先读取空参数的 constructor。按业务契约拒绝或处理缺省参数,保留零和非法值的区别。

引入 Day.js 的 objectSupport 插件后,原本接收表单天数的代码开始报 Cannot read properties of null (reading 'constructor'),传入 undefined 时也会失败。日期本身可能完全有效,异常发生在插件检查运算参数的阶段。

这条路径包括 add(null, 'day')、subtract(undefined, 'day'),以及把 null 当作 set 的单位传入。已核对正式 Day.js 1.11.23:三个操作的插件包装都先读取参数的 constructor,尚未把空值交给原来的方法。

解析日期的空值防护,没有覆盖运算包装

objectSupport 让日期解析与操作方法接受对象形式的参数。它的日期解析分支会先排除 null、undefined;add、subtract、set 的包装却直接检查 argument.constructor.name。

因此,dayjs(null) 的处理是否正常,不能证明 someDate.add(null, 'day') 也有同样的保护。两者是不同入口。其他插件是否改变了行为,也应与实际安装顺序一起核对,不能只看页面里最终显示了什么日期。

先保留完整异常栈,找到读取 constructor 的 objectSupport 包装,再查看该次调用实际收到的值。表单未填、JSON 中明确的 null、读取不到的对象字段,都可能来到同一行;不要先给日期字符串补格式,或将所有失败都归为时区问题。

在业务入口决定空值是什么意思

如果业务规定天数必须由用户提供,就在调用日期方法之前拒绝空值和非有限数字。下面的函数只接收日期文本与数值天数,不接受 objectSupport 的对象式运算参数:

import dayjs from 'dayjs';

function addDaysChecked(dateText, amount) {
  if (typeof amount !== 'number' || !Number.isFinite(amount)) {
    throw new TypeError('amount 必须是有限数字');
  }
  const date = dayjs(dateText);
  if (!date.isValid()) {
    throw new RangeError('日期无效');
  }
  return date.add(amount, 'day');
}

字符串数字应在现有输入解析层按明确格式转换;不要对任意值调用 Number 后就认为有效,因为 null 和空字符串都能变成零。是否接受小数天数也需要业务规则:Day.js 对天、周的数值处理有取整语义,不能因通过有限数字检查就假定它按小时比例累加。

若产品明确把“缺省天数”定义为零,可以只对允许的缺省状态赋零,再校验其他输入。不要为了消除异常,把用户明确传入的 null、拼写错误和真正的零一并吞掉。默认参数不会替换显式 null,只给函数写一个默认值也未必覆盖当前请求。

set 的第一个参数是单位或对象,不应套用“天数赋零”的处理。对单位采用业务允许的明确名称;缺少单位时拒绝调用,不要把 null 字符串化成一个看似有效的名称。

不必覆盖全局原型

这个入口已经可以在应用边界处理,无须修改 Day.js 私有方法或保存一份旧 add 再全局替换。objectSupport 还承担其他对象式参数的能力,直接移除插件可能影响正常调用;应先核对现有使用范围。

保留原配置后,在受控流程比较缺省、null、undefined、零、合法正负天数以及非法字符串。无效输入应回到原有表单或接口错误路径,合法输入仍返回预期日期;同时核对原先真正需要的对象式日期参数。不要仅以不再抛 TypeError 为成功标准,最后显示一个 Invalid Date 或悄悄按零处理,也可能把问题藏了起来。