当前位置: 首页 > web >正文

【相等性比较的通解——理解 JavaScript 中的 Object.is()】

相等性比较的通解——理解 JavaScript 中的 Object.is()

一、引言

在 JavaScript 中,相等性比较一直是一个复杂的话题。ES6 引入的 Object.is() 方法为我们提供了一种新的相等性判断机制,它解决了 ===== 运算符的一些历史遗留问题。本文将深入探讨 Object.is() 的方方面面。

二、历史背景

Object.is() 是在 ECMAScript 6 (ES2015) 中引入的,其提出过程如下:

  • 2013年:首次在 ES6 规范草案中提出
  • 2015年:随 ES6 正式发布
  • 目的:提供一种更严格的相等性比较机制,称为"Same-value equality"(同值相等)

主流浏览器支持时间线

  • Chrome: 30+ (2013年9月)
  • Firefox: 29+ (2014年4月)
  • Safari: 9+ (2015年9月)
  • Edge: 12+ (2015年7月)

三、Object.is() 的实现原理

3.1 基本实现

Object.is() 的 polyfill 实现揭示了其核心原理:

if (!Object.is) {Object.is = function(x, y) {// 处理 NaNif (x !== x) {return y !== y;}// 处理 +0 和 -0if (x === 0 && y === 0) {return 1 / x === 1 / y;}// 其他情况return x === y;};
}

3.2 特殊情况处理

Object.is() 主要解决了两个特殊情况:

  • NaN 的比较

    Object.is(NaN, NaN)      // true
    NaN === NaN              // false
    
  • 零值的比较

    Object.is(+0, -0)        // false
    +0 === -0               // true
    

四、与其他比较方式的对比

JavaScript 中存在四种主要的相等性比较方式:

// 1. == (Abstract Equality Comparison)
'' == false              // true(存在类型转换)
1 == '1'                // true// 2. === (Strict Equality Comparison)
'' === false            // false
+0 === -0              // true
NaN === NaN            // false// 3. Object.is (Same-value equality)
Object.is('', false)    // false
Object.is(+0, -0)       // false
Object.is(NaN, NaN)     // true// 4. Same-value-zero equality (Array.includes)
['foo'].includes('foo') // true
[NaN].includes(NaN)    // true
[+0].includes(-0)      // true

五、基本类型的比较

5.1 为什么选择 Object.is

Object.is 是比较基本类型最靠谱的方式,原因如下:

  • 处理了 JavaScript 中的特殊数值情况(+0/-0, NaN)
  • 不进行类型转换,避免了意外结果
  • 符合直觉的相等性判断
  • 在处理基本类型时提供了最严格和准确的比较
  • 被 ECMAScript 规范采用作为"同值相等"的标准实现
  • 在现代框架(如 React)中被广泛使用

5.2 null 和 undefined 的特殊性

nullundefinedObject.is 中的处理很特别:

// 它们与自身比较
Object.is(null, null)           // true
Object.is(undefined, undefined) // true// 它们互相比较
Object.is(null, undefined)      // false

这是因为它们各自都是其类型的唯一值:

  • null 是 Null 类型的唯一值
  • undefined 是 Undefined 类型的唯一值

六、在现代框架中的应用

6.1 React 中的应用

// useState 的内部实现
function basicStateReducer(state, action) {return Object.is(state, action) ? state : action;
}// React.memo 的默认比较函数
function areEqual(prevProps, nextProps) {return Object.is(prevProps, nextProps);
}

6.2 Vue 3 中的应用

// Vue 3 响应式系统
function hasChanged(value, oldValue) {return !Object.is(value, oldValue);
}

七、性能考虑

Object.is 的性能与 === 相近,在大多数现代引擎中已经被优化。但在某些场景下需要注意使用方式:

// 不推荐(性能不好)
array.findIndex(item => Object.is(item, searchValue))// 推荐
array.findIndex(item => item === searchValue) // 对于普通值
array.findIndex(item => Number.isNaN(item))   // 对于 NaN

八、最佳实践

8.1 数值比较

// 精确的零值比较
function isNegativeZero(value) {return Object.is(value, -0);
}// NaN 检测
function isReallyNaN(value) {return Object.is(value, NaN);
}

8.2 状态比较

// 状态更新检测
function hasStateChanged(oldState, newState) {return !Object.is(oldState, newState);
}

8.3 类型检查

// null 或 undefined 检查
function isNullOrUndefined(value) {return Object.is(value, null) || Object.is(value, undefined);
}

九、实际应用场景

9.1 数据验证

function validateNumber(value) {if (Object.is(value, NaN)) {throw new Error('Invalid number');}if (Object.is(value, -0)) {// 特殊处理负零的情况return +0;}return value;
}

9.2 状态管理

class StateManager {#state = {};setState(newState) {if (!Object.is(this.#state, newState)) {this.#state = newState;this.notifyUpdate();}}
}

9.3 缓存优化

const memoize = (fn) => {const cache = new Map();return (...args) => {const key = JSON.stringify(args);const cached = cache.get(key);if (cached && Object.is(cached.args, args)) {return cached.result;}const result = fn(...args);cache.set(key, { args, result });return result;};
};

十、总结

Object.is() 的引入标志着 JavaScript 在处理相等性比较方面的重要进步。它提供了比 === 更严格的相等性比较,解决了 NaN±0 的比较问题,并已被现代框架广泛采用。理解和正确使用 Object.is() 对于编写可靠的 JavaScript 代码至关重要。

在实际开发中,我们应该根据具体场景选择合适的比较方式:

  • 需要严格的值比较时,使用 Object.is()
  • 需要考虑性能的普通比较时,使用 ===
  • 需要类型转换的比较时,使用 ==

通过深入理解 Object.is(),我们能够在代码中更好地处理相等性比较,提高代码的可靠性和可维护性。


附录:数组里性能差异原因

性能差异分析:Object.is vs ===

函数调用开销

// 版本1:使用 Object.is
array.findIndex(item => Object.is(item, searchValue))// 版本2:使用 ===
array.findIndex(item => item === searchValue)

主要区别

  • Object.is 是一个函数调用,每次比较都需要创建一个新的函数调用栈帧
  • === 是语言内置的运算符,直接在 CPU 层面执行

执行步骤对比

// 使用 Object.is 时的步骤
array.findIndex(item => {// 1. 创建函数调用栈帧// 2. 传递参数// 3. 执行 Object.is 的内部逻辑// 4. 返回结果// 5. 销毁栈帧return Object.is(item, searchValue);
});// 使用 === 时的步骤
array.findIndex(item => {// 1. 直接执行 CPU 级别的比较操作return item === searchValue;
});

性能测试示例

const arr = new Array(1000000).fill(1);
const searchValue = 2;console.time('Object.is');
arr.findIndex(item => Object.is(item, searchValue));
console.timeEnd('Object.is');console.time('===');
arr.findIndex(item => item === searchValue);
console.timeEnd('===');// 典型输出:
// Object.is: 8.123ms
// ===: 3.456ms

更详细的性能分析

// 1. 单次操作的性能比较
console.time('Single Object.is');
Object.is(1, 2);
console.timeEnd('Single Object.is');console.time('Single ===');
1 === 2;
console.timeEnd('Single ===');// 2. 循环中的性能比较
console.time('Loop Object.is');
for(let i = 0; i < 1000000; i++) {Object.is(1, 2);
}
console.timeEnd('Loop Object.is');console.time('Loop ===');
for(let i = 0; i < 1000000; i++) {1 === 2;
}
console.timeEnd('Loop ===');

在不同场景下的影响

// 场景1:单次比较
const a = 1, b = 2;
Object.is(a, b);     // 性能影响可以忽略
a === b;             // 性能最优// 场景2:大数组遍历
const largeArray = new Array(1000000).fill(1);// 不推荐:每次迭代都要调用函数
largeArray.findIndex(item => Object.is(item, 2));// 推荐:直接使用运算符
largeArray.findIndex(item => item === 2);

特殊情况的处理

// 对于 NaN 的查找
const arrayWithNaN = [1, NaN, 2, 3];// 方法1:使用 Object.is(虽然准确但性能较差)
arrayWithNaN.findIndex(item => Object.is(item, NaN));// 方法2:使用 Number.isNaN(更优的方案)
arrayWithNaN.findIndex(item => Number.isNaN(item));

优化建议

// 不好的实现
function findInArray(arr, value) {return arr.findIndex(item => Object.is(item, value));
}// 更好的实现
function findInArray(arr, value) {// 对特殊情况进行处理if (Number.isNaN(value)) {return arr.findIndex(Number.isNaN);}if (Object.is(value, -0)) {return arr.findIndex(item => Object.is(item, -0));}// 普通情况使用 ===return arr.findIndex(item => item === value);
}

总结

  • 虽然单次 Object.is=== 的性能差异很小,但在大量重复调用(如数组方法)中,这个差异会被放大
  • 函数调用的开销(创建栈帧、传参、返回值等)是造成性能差异的主要原因
  • 在循环或数组方法中,应优先使用 ===,除非确实需要 Object.is 的特殊处理(如 NaN±0 的比较)
  • 对于特殊值的比较,可以使用更专门的方法(如 Number.isNaN)来获得更好的性能

这就是为什么在数组方法中推荐使用 === 而不是 Object.is 的原因。这是在准确性和性能之间做出的权衡。


为什么要出现Object.is()这个函数?

Object.is 的出现原因及其与 === 的区别

Object.is 主要解决的问题

// 1. NaN 的比较问题
NaN === NaN                // false
Object.is(NaN, NaN)       // true// 2. 零值的区分问题
+0 === -0                 // true
Object.is(+0, -0)         // false

为什么要专门出一个 Object.is

主要原因

  • 提供一个更符合数学概念的相等性比较方法
  • 解决 JavaScript 中一些历史遗留的特殊情况
  • 在语言层面统一相等性比较的标准

具体场景分析

// 1. NaN 的问题
// 在数学中,NaN 应该等于自身,但在 JS 中:
NaN === NaN              // false(这是一个反直觉的结果)
isNaN(NaN)              // true(传统解决方案)
Number.isNaN(NaN)       // true(ES6 解决方案)
Object.is(NaN, NaN)     // true(更直观的方案)// 2. 零值的问题
// 在某些数学计算中,+0 和 -0 的区别很重要:
1 / +0                  // Infinity
1 / -0                  // -Infinity// === 无法区分这种情况:
+0 === -0              // true// Object.is 可以区分:
Object.is(+0, -0)      // false

在实际应用中的意义

// 1. 在数学计算中
function divideByZero(value) {// 这里区分 +0 和 -0 是有意义的if (Object.is(value, -0)) {return '-Infinity';}if (Object.is(value, +0)) {return 'Infinity';}
}// 2. 在数值验证中
function validateNumber(value) {if (Object.is(value, NaN)) {throw new Error('Invalid number');}// 其他验证逻辑
}// 3. 在现代框架中(如 React)
function shouldComponentUpdate(nextProps) {// 使用 Object.is 可以更准确地判断值是否真的改变return !Object.is(this.props.value, nextProps.value);
}

为什么不直接修改 === 的行为?

主要考虑

  • 向后兼容性 - 修改 === 的行为会破坏大量现有代码
  • 性能考虑 - === 作为基础运算符需要保持高性能
  • 渐进式改进 - 通过新的 API 来提供更好的解决方案

使用建议

// 1. 普通值比较,使用 ===
if (value === 42) { ... }// 2. 需要准确判断 NaN 时
if (Object.is(value, NaN)) { ... }
// 或者更好的方式
if (Number.isNaN(value)) { ... }// 3. 需要区分 +0 和 -0 时
if (Object.is(value, -0)) { ... }// 4. 在框架或库的实现中,需要严格的值比较时
if (Object.is(oldValue, newValue)) { ... }

总结

  • Object.is 的主要目的是提供一个更符合数学直觉的相等性比较方法
  • 它主要解决了 NaN±0 的比较问题
  • 对于 nullundefined 的处理,它与 === 是一致的
  • 它的出现不是为了替代 ===,而是提供一个更严格的相等性比较选项

在实际开发中,应该根据具体需求选择合适的比较方式:

  • 普通比较用 ===
  • 需要严格数学比较时用 Object.is
  • 特殊值检测用专门的方法(如 Number.isNaN

这样设计的好处是既保持了语言的向后兼容性,又提供了更准确的值比较方案,同时还保留了不同场景下的性能优化空间。

http://www.xdnf.cn/news/10828.html

相关文章:

  • 高考数学易错考点02 | 临阵磨枪
  • 深入解析Playwright for Python:浏览器功能与代码实例详解
  • 【Visual Studio 2022】卸载安装,ASP.NET
  • Go Gin框架深度解析:高性能Web开发实践
  • LabVIEW磁悬浮轴承传感器故障识别
  • Windows版PostgreSQL 安装 vector 扩展
  • 服务器被攻击了怎么办
  • pikachu靶场通关笔记11 XSS关卡07-XSS之关键字过滤绕过(三种方法渗透)
  • 华为盘古 Ultra MoE 模型:国产 AI 的技术突破与行业影响
  • 每日算法刷题Day21 6.3:leetcode二分答案2道题,用时1h20min(有点慢)
  • metersphere不同域名的参数在链路测试中如何传递?
  • 【MATLAB代码】制导——三点法,二维平面下的例程|运动目标制导,附完整源代码
  • 采摘机器人项目
  • dvwa5——File Upload
  • 1.6万字测评:deepseek-r1-0528横向对比 gemini-2.5-pro-0506和claude4
  • Cursor + Claude 4:海外工具网站开发变现实战案例
  • 基于PyQt5的相机手动标定工具:原理、实现与应用
  • 【Qt】构建目录设置
  • 从0开始学习R语言--Day16--倾向得分匹配
  • 相机--相机成像原理和基础概念
  • Cursor + Claude 4:微信小程序流量主变现开发实战案例
  • Springboot中Controller接收参数的方式
  • 功能管理:基于 ABP 的 Feature Management 实现动态开关
  • iptables常用命令
  • Spring Boot + MyBatis-Plus 读写分离与多 Slave 负载均衡示例
  • MyBatis 执行 SQL 报错:String 无法转换为 Long 的排查与解决实录
  • 【Linux内核】设备模型之udev技术详解
  • Unity异常上报飞书工具
  • 如何计算H5页面加载时的白屏时间
  • llama.cpp:纯 C/C++ 实现的大语言模型推理引擎详解一