为什么 NaN 不等于自身?怎么可靠地判断一个值是不是 NaN?
NaN 与自身用 === 比较也为 false,这是 IEEE 754 的相等规则。Number.isNaN 不做类型转换,全局 isNaN 会先转换输入;数组的 indexOf 找不到 NaN,includes 却能找到,因为两种方法使用的相等算法不同。
JavaScript面试题第 6 页,显示第 251–285 题,共找到 285 道完整解析,可继续按分类、标签与关键词缩小范围。
按稳定语义路径排序
NaN 与自身用 === 比较也为 false,这是 IEEE 754 的相等规则。Number.isNaN 不做类型转换,全局 isNaN 会先转换输入;数组的 indexOf 找不到 NaN,includes 却能找到,因为两种方法使用的相等算法不同。
IEEE 754 保留了正零和负零的区别。JS 里 -0 === 0 为 true,但 Object.is 能区分,1 除以 -0 得到 -Infinity。搞清负零怎么产生、怎么检测、以及 JSON 序列化会丢失符号。
?? 只在左侧为 null 或 undefined 时启用默认值,|| 遇到零、false 或空字符串也会回退,因此处理合法业务值时结果不同。?? 与 &&、|| 直接混用有语法限制,??= 只在空值时赋值,可选链常与它一起处理缺失字段。
JavaScript 的 Number 采用 IEEE 754 双精度格式,包含符号位、偏移指数和有效数字尾数。指数影响可表示范围,尾数限制精度;隐含的最高有效位解释了安全整数边界,非规格化数则关系到最小正数和下溢。
全局 isFinite 会先做数值转换,因此字符串 "42"、null 与空字符串都可能得到 true;Number.isFinite 只接受真正的有限 number。表单输入应先明确解析规则,再检查是否有限;BigInt 和无穷大也不能混为一谈。
parseInt 会从字符串开头读取可识别的整数部分,碰到不合法字符就停止,一个数字都读不到才是 NaN。radix 指定进制,0x 前缀有特殊处理;把它直接传给 map 或用于极小浮点数时,隐式参数和科学计数法会制造意外结果。
Number() 和一元加号要求输入整体符合数字转换规则,parseInt 与 parseFloat 则能在前缀合法时提前停止。空串、带单位文本、进制前缀和科学计数法因此会得到不同结果;处理用户输入时应先决定是否允许尾部杂质。
toFixed 返回字符串,并按 Number 实际保存的二进制值舍入;十进制的 1.005 不能精确存储,保留两位可能得到 1.00。数值足够大时还会改用指数记法,金额计算应避免把展示格式化当成精确运算。
Number 的 toString(radix) 可把数值表示为 2 到 36 进制字符串,负数输出负号而非补码。小数转换可能无法有限结束,补零又是字符串格式问题;超过 Number 安全整数范围的输入应先保留精度,再考虑用 BigInt 转换。
0.1 和 0.2 不能用有限二进制小数精确表示,Number 存储时发生舍入,相加后会看到 0.30000000000000004。用 Number.EPSILON 比较只适合相应数量级的误差,金额运算通常需整数分或十进制定点方案。
Number.MIN_VALUE 是最小正的非零浮点数,约为 5e-324,和最小安全整数毫无关系。更小的正数会下溢;正负零在 === 下相等、在 Object.is 下不同,除法等运算还能保留或暴露负零符号。
数字分隔符允许在字面量的数字之间插入下划线,十进制、其他进制、BigInt 和指数部分都能用,但不能放在开头、结尾或紧挨小数点。它只是源码书写语法,Number("1_000") 不会按分隔符解析,反而得到 NaN。
typeof 对数组、日期等对象只能给出粗略的 object 结果,Object.prototype.toString.call 可得到 [object Array] 标签。借用基类方法可避开实例自己的 toString;Symbol.toStringTag 却能改写标签,判断数组应使用 Array.isArray。
对象转原始值时,字符串上下文通常先尝试 toString,数字和默认上下文通常先尝试 valueOf;Symbol.toPrimitive 存在时会优先接管。返回对象而非原始值可能触发回退或异常,Date 的默认转换又与一般对象不同。
a = b === c 会先比较 b 与 c,再把布尔结果赋给 a,因为相等比较的优先级高于赋值。连续赋值还涉及结合方向;在 if 条件中少写一个等号、取反位置写错或混入位运算符,都可能让表达式合法却语义错误。
一元加号和 Number() 对常见字符串、布尔值、null、undefined 与对象使用相同的数值转换规则,但对 BigInt 不同:Number(10n) 可显式转换,+10n 会抛 TypeError。它们与 parseInt 的前缀解析也不同,处理输入要避免静默截断。
读取字符串等原始值的方法时,引擎会临时提供对应包装对象,从原型上找到方法,用完不保留这层包装。给原始值添加自定义属性不会形成持久状态;new String 等显式包装对象又有不同的真值与相等行为,null 和 undefined 更不能这样访问。
Object.is 与 === 的差别集中在 NaN 和正负零;includes、Set、Map 使用的 SameValueZero 则把 NaN 视为相同,也不区分正负零。React 的状态比较采用 Object.is,遇到负零或 NaN 更新时,不能直接拿 === 的结果推断。
加号会先把操作数转为原始值,只要其中一个是字符串就拼接,否则再做数值相加。因此 1 + 2 + "3" 与 "1" + 2 + 3 的结果不同;对象、数组及 null 参与时还要看转换顺序,减号却不会执行字符串拼接。
JavaScript 字符串用反斜杠表示换行、引号和反斜杠本身;十六进制与 Unicode 转义还能写出指定码点。四位 Unicode 转义放不下部分表情符号,需用花括号码点形式;正则表达式和 Windows 路径经过多层字符串解析时尤其容易多写或漏写反斜杠。
字符串下标可以读取 UTF-16 码元,却不能像数组下标那样改写字符;严格模式下给只读索引赋值还会报错。charAt、at 与方括号对越界和负索引的处理不同,表情符号被下标切开时还可能只取到代理项的一半。
字符串 length 统计 UTF-16 码元,常用汉字通常占一个码元,许多表情符号占两个,家庭表情等组合序列还会占更多。展开运算符按码点遍历,Intl.Segmenter 才能更贴近用户看到的字素簇;昵称截断要选对单位。
预组合的 é 与 e 加组合重音符视觉相同,底层码点不同,直接用 === 比较会失败。normalize 可转换成 NFC、NFD 等形式以便搜索和去重;兼容正规化还可能改变字符含义,用户名校验与文件名处理要谨慎选用。
slice、substring、substr 在普通正数范围下看起来相似,负数和起止颠倒时却明显不同:slice 可从末尾计数,substring 会把负数当零并交换边界,substr 的第二个参数是长度。新代码优先选语义清楚的 slice,避免依赖遗留方法。
String 包装对象的 valueOf 与 toString 都返回内部原始字符串,但对象转换时先调用谁取决于上下文。模板插值偏向字符串转换,加号通常采用默认转换;重写其中一个方法后,相同对象在两种表达式中可能显示不同结果。
Symbol 属性键不会与同名字符串键冲突,也不会出现在 Object.keys、for...in 或 JSON.stringify 的普通对象属性结果中。Reflect.ownKeys 能读到它,展开运算符与 Object.assign 还会复制可枚举 Symbol 键,所以隐藏不等于私有。
每次调用 Symbol() 都创建新的唯一原始值,描述文字相同也不会相等;Symbol.for 则通过全局注册表复用键。Symbol 不能用 new 创建,隐式转成字符串也会失败;作为对象属性键时它能避免普通字符串命名冲突。
模板插值会按字符串上下文转换对象,加号拼接通常先走默认原始值转换;自定义 valueOf 和 toString 后,两种写法可能得到不同文本。Symbol 的隐式转换还有特别限制,嵌套模板与标签模板也不等同于普通字符串拼接。
JSON.stringify 遇到 undefined、函数或 Symbol 时,在对象属性里会省略,在数组位置会变成 null,作为顶层值则返回 undefined。NaN、Date、BigInt 与循环引用又有不同处理结果;replacer 能控制部分字段,但不能直接解决所有不可序列化值。
对象参与运算时,Symbol.toPrimitive 若存在会优先接收 number、string 或 default 提示,决定对象如何变成原始值。没有该方法时才按顺序尝试 valueOf 和 toString;Date 在默认提示下偏向字符串,因此日期对象与数字相加可能得到拼接结果。
字符串是不可变的原始值,截取、替换等方法返回新字符串;数组是对象,push、sort、splice 等方法会直接改动自身。两者在传参和 React 状态更新中容易被混淆,数组若需保留原值可用 toSorted、toSpliced 等非修改方法。
typeof 对数组、Date、Map、正则、包装对象和 null 都返回 object,无法单凭结果细分对象种类。判断空值要直接与 null 比较,判断数组用 Array.isArray;未声明变量与暂时性死区中的变量对 typeof 也并非同样安全。
undefined 常代表尚未赋值或缺少参数,null 通常是代码主动放入的空值。两者在 typeof、宽松相等、数字转换、默认参数和 JSON 序列化中有不同结果;接口字段需要表达“缺失”还是“明确清空”时,这个区别尤其重要。
Number 包装对象的 valueOf 返回内部保存的数字;自定义对象参与加减法或比较时,valueOf 也可能成为取得原始值的第一步。若 Symbol.toPrimitive 存在,它会优先接管转换;返回的仍是对象时还可能继续尝试 toString 或抛错。
void 会先求值右侧表达式,再把整个表达式的结果固定为 undefined。void 0 曾被用来取得可靠的 undefined,如今也常用来显式丢弃回调返回值或标记不等待的 Promise;它不会吞掉 Promise 的未处理拒绝。