MK
摩柯社区 - 一个极简的技术知识社区
AI 面试

JavaScript中的对象与原始值比较

2022-02-075.7k 阅读

JavaScript 中的基本数据类型与对象

在深入探讨 JavaScript 中对象与原始值比较之前,我们先来回顾一下 JavaScript 的数据类型。JavaScript 中有两种主要的数据类型:原始数据类型(Primitive Types)和对象(Object)。

原始数据类型

JavaScript 定义了七种原始数据类型,分别是:

  1. 布尔值(Boolean):只有两个值,truefalse,用于逻辑判断。例如:
let isDone = true;
let isFailed = false;
  1. 数字(Number):表示整数和浮点数。在 JavaScript 中,所有数字都是以 64 位浮点格式表示的。比如:
let num1 = 42;
let num2 = 3.14;
  1. 字符串(String):是由零个或多个 16 位 Unicode 字符组成的序列。字符串可以用单引号(')、双引号(")或反引号(```)来表示。例如:
let str1 = 'Hello';
let str2 = "World";
let str3 = `JavaScript`;
  1. 空值(Null):表示一个空值,它只有一个值 null,通常用于表示变量尚未指向任何对象。
let myNull = null;
  1. 未定义(Undefined):当一个变量被声明但未被赋值时,它的值就是 undefined
let myVar;
console.log(myVar); // 输出: undefined
  1. 符号(Symbol):是 ES6 引入的一种新的原始数据类型,它表示独一无二的值。
let sym1 = Symbol('description');
let sym2 = Symbol('description');
console.log(sym1 === sym2); // 输出: false
  1. 大整数(BigInt):是 ES2020 引入的,用于表示任意精度的整数。可以通过在数字后面加 n 来创建一个 BigInt。
let bigInt1 = 123456789012345678901234567890n;

对象

对象是 JavaScript 中的复合数据类型,它是一个无序的属性集合,每个属性都是一个名值对。对象可以通过对象字面量、new 关键字以及构造函数等方式创建。

  1. 对象字面量:这是创建对象最常见的方式,使用花括号 {} 来定义对象及其属性。
let person = {
  name: 'John',
  age: 30,
  isStudent: false
};
  1. 使用 new 关键字和构造函数
function Animal(name, species) {
  this.name = name;
  this.species = species;
}
let dog = new Animal('Buddy', 'Dog');
  1. 使用 Object.create():该方法创建一个新对象,使用现有的对象来提供新创建对象的 __proto__
let proto = {
  greet: function() {
    console.log('Hello!');
  }
};
let newObj = Object.create(proto);
newObj.greet(); // 输出: Hello!

原始值与对象的比较方式

严格相等(===

在 JavaScript 中,严格相等运算符 === 比较两个值是否严格相等,即它们不仅值要相等,数据类型也要相同。对于原始值和对象,这种比较有着明显的区别。

  1. 原始值之间的严格相等比较
    • 布尔值:
console.log(true === true); // 输出: true
console.log(true === false); // 输出: false
  • 数字:
console.log(42 === 42); // 输出: true
console.log(42 === 43); // 输出: false
console.log(3.14 === 3.14); // 输出: true
  • 字符串:
console.log('Hello' === 'Hello'); // 输出: true
console.log('Hello' === 'World'); // 输出: false
  • 对于 nullundefined,它们有特殊的行为。虽然它们是不同的数据类型,但 null === undefined 返回 false,只有 null === nullundefined === undefined 分别返回 true
console.log(null === null); // 输出: true
console.log(undefined === undefined); // 输出: true
console.log(null === undefined); // 输出: false
  • 符号(Symbol):每个符号都是独一无二的,即使描述相同,两个符号也不相等。
let sym1 = Symbol('test');
let sym2 = Symbol('test');
console.log(sym1 === sym2); // 输出: false
  • 大整数(BigInt):只有值相等的大整数才严格相等。
let bigInt1 = 123n;
let bigInt2 = 123n;
let bigInt3 = 456n;
console.log(bigInt1 === bigInt2); // 输出: true
console.log(bigInt1 === bigInt3); // 输出: false
  1. 对象与原始值的严格相等比较:对象和原始值永远不会严格相等,因为它们的数据类型不同。
let num = 42;
let obj = { value: 42 };
console.log(num === obj); // 输出: false
  • 即使对象的属性值与原始值相同,也不相等。
let str = 'Hello';
let objStr = { value: 'Hello' };
console.log(str === objStr); // 输出: false
  • 对于两个不同的对象实例,即使它们具有相同的属性和值,它们也不严格相等。
let obj1 = { name: 'John', age: 30 };
let obj2 = { name: 'John', age: 30 };
console.log(obj1 === obj2); // 输出: false

这是因为对象在内存中是通过引用存储的,obj1obj2 虽然属性和值相同,但它们在内存中的位置不同,是两个不同的引用。

宽松相等(==

宽松相等运算符 == 在比较时会进行类型转换,然后再比较值。

  1. 原始值之间的宽松相等比较
    • 布尔值与数字:true 会转换为 1false 会转换为 0 进行比较。
console.log(true == 1); // 输出: true
console.log(false == 0); // 输出: true
console.log(true == 0); // 输出: false
  • 字符串与数字:字符串会尝试转换为数字进行比较。
console.log('42' == 42); // 输出: true
console.log('abc' == 42); // 输出: false,因为 'abc' 无法转换为有效的数字
  • nullundefined:在宽松相等比较中,nullundefined 彼此相等。
console.log(null == undefined); // 输出: true
  • 符号(Symbol):符号不能转换为其他类型进行宽松相等比较,所以符号与其他任何值(除了它自身通过 === 比较)宽松相等比较都为 false
let sym = Symbol('test');
console.log(sym == 'test'); // 输出: false
  • 大整数(BigInt):如果一方是数字,另一方是大整数,大整数会转换为数字进行比较,但这种转换可能会导致精度丢失。
let bigInt = 123n;
let num = 123;
console.log(bigInt == num); // 输出: true
let bigIntLarge = 9007199254740993n;
let numLarge = 9007199254740993;
console.log(bigIntLarge == numLarge); // 输出: false,因为转换为数字时精度丢失
  1. 对象与原始值的宽松相等比较
    • 对象与数字比较:对象会尝试转换为原始值,然后再进行比较。对象转换为原始值的过程首先调用 valueOf() 方法,如果 valueOf() 返回的不是原始值,则调用 toString() 方法。
let obj = { valueOf: function() { return 42; } };
console.log(obj == 42); // 输出: true
  • 对象与字符串比较:同样,对象先尝试转换为原始值。
let objStr = { toString: function() { return 'Hello'; } };
console.log(objStr == 'Hello'); // 输出: true
  • 对象与布尔值比较:对象会转换为原始值,然后布尔值转换为数字,再进行比较。
let objBool = { valueOf: function() { return 1; } };
console.log(objBool == true); // 输出: true

对象转换为原始值的细节

valueOf() 方法

valueOf() 方法用于返回指定对象的原始值。默认情况下,Object.prototype.valueOf() 返回对象本身。但许多内置对象重写了该方法以返回合适的原始值。

  1. 数字对象(NumberNumber 对象的 valueOf() 方法返回它表示的数字。
let numObj = new Number(42);
console.log(numObj.valueOf()); // 输出: 42
  1. 字符串对象(StringString 对象的 valueOf() 方法返回它表示的字符串。
let strObj = new String('Hello');
console.log(strObj.valueOf()); // 输出: 'Hello'
  1. 布尔对象(BooleanBoolean 对象的 valueOf() 方法返回它表示的布尔值。
let boolObj = new Boolean(true);
console.log(boolObj.valueOf()); // 输出: true
  1. 自定义对象:如果自定义对象没有重写 valueOf() 方法,它将继承自 Object.prototype.valueOf(),返回对象本身。在宽松相等比较中,如果 valueOf() 返回的不是原始值,则会继续调用 toString() 方法。
let myObj = { name: 'John' };
console.log(myObj.valueOf()); // 输出: { name: 'John' }

toString() 方法

toString() 方法用于返回一个表示该对象的字符串。所有对象都继承了 toString() 方法。如果对象没有自己的 toString() 方法,它将使用 Object.prototype.toString(),该方法返回 [object ObjectName] 形式的字符串,其中 ObjectName 是对象的类型。

  1. 数字对象(NumberNumber 对象的 toString() 方法将数字转换为字符串。
let num = 42;
let numObj = new Number(num);
console.log(numObj.toString()); // 输出: '42'
  1. 字符串对象(StringString 对象的 toString() 方法返回字符串本身。
let str = 'Hello';
let strObj = new String(str);
console.log(strObj.toString()); // 输出: 'Hello'
  1. 布尔对象(BooleanBoolean 对象的 toString() 方法返回 'true''false'
let bool = true;
let boolObj = new Boolean(bool);
console.log(boolObj.toString()); // 输出: 'true'
  1. 自定义对象:如果自定义对象没有重写 toString() 方法,它将使用 Object.prototype.toString()。但在宽松相等比较中,这种默认的 toString() 方法返回的字符串在与原始值比较时通常不会得到预期的结果。所以通常需要重写 toString() 方法以返回合适的字符串表示。
let myObj = { name: 'John' };
console.log(myObj.toString()); // 输出: '[object Object]'
  • 例如,我们可以重写 toString() 方法以满足比较需求。
let myObj2 = {
  name: 'John',
  toString: function() {
    return this.name;
  }
};
console.log(myObj2 == 'John'); // 输出: true

比较中的特殊情况

NaN 的比较

NaN(Not a Number)是一个特殊的数值,表示一个非法的数字操作结果。NaN 与任何值(包括它自身)进行严格相等(===)或宽松相等(==)比较都返回 false

let num1 = NaN;
let num2 = NaN;
console.log(num1 === num2); // 输出: false
console.log(num1 == num2); // 输出: false

要判断一个值是否为 NaN,可以使用 isNaN() 函数或者 ES6 中的 Number.isNaN() 方法。isNaN() 函数会先尝试将参数转换为数字,如果转换后是 NaN 则返回 true,这可能会导致一些意外结果。而 Number.isNaN() 只对真正的 NaN 返回 true

console.log(isNaN('abc')); // 输出: true,因为 'abc' 转换为数字是 NaN
console.log(Number.isNaN('abc')); // 输出: false,因为 'abc' 不是真正的 NaN
console.log(Number.isNaN(NaN)); // 输出: true

数组的比较

数组在 JavaScript 中是对象,它们遵循对象的比较规则。两个不同的数组实例,即使它们的元素相同,严格相等(===)比较也返回 false

let arr1 = [1, 2, 3];
let arr2 = [1, 2, 3];
console.log(arr1 === arr2); // 输出: false

在宽松相等(==)比较中,数组会尝试转换为原始值。数组的 valueOf() 方法返回数组本身,不是原始值,所以会调用 toString() 方法,toString() 方法将数组元素转换为字符串并以逗号分隔连接起来。

let arr3 = [1, 2, 3];
console.log(arr3 == '1,2,3'); // 输出: true

函数的比较

函数也是对象,遵循对象的比较规则。两个不同的函数实例,严格相等(===)比较返回 false

function func1() {}
function func2() {}
console.log(func1 === func2); // 输出: false

在宽松相等(==)比较中,函数同样先尝试通过 valueOf() 转换为原始值,由于 valueOf() 返回函数本身,不是原始值,再调用 toString() 方法,toString() 方法返回函数的字符串表示,一般是函数的定义。但这种字符串表示在宽松相等比较中很少能与其他常见原始值匹配。

function func3() {}
console.log(func3 == 'function func3() {}'); // 输出: false

类型转换规则总结

在 JavaScript 的对象与原始值比较中,宽松相等(==)涉及到复杂的类型转换规则。以下是一些常见的类型转换总结:

  1. 布尔值转换true 转换为 1false 转换为 0 与数字比较。
  2. 字符串转换:如果一方是字符串,另一方是数字,字符串会尝试转换为数字。如果转换失败,比较结果为 false
  3. 对象转换:对象在与原始值比较时,先调用 valueOf() 方法,如果返回的不是原始值,再调用 toString() 方法。
  4. nullundefined:在宽松相等比较中,nullundefined 彼此相等,且它们与其他类型比较时,除了与 nullundefined 比较外,结果通常为 false
  5. 符号(Symbol):符号不能转换为其他类型进行宽松相等比较,除自身 === 比较外,与其他值比较都为 false
  6. 大整数(BigInt):与数字比较时,大整数可能转换为数字,但要注意精度丢失问题。

理解这些类型转换和比较规则对于编写健壮的 JavaScript 代码至关重要。在实际开发中,应尽量使用严格相等(===)比较,以避免因类型转换带来的意外结果。但在某些特定场景下,宽松相等(==)也有其用武之地,比如处理用户输入可能是不同类型但逻辑上应视为相等的情况。不过,无论使用哪种比较方式,都需要清楚其背后的机制,以确保代码的正确性和可维护性。