关于js中对象类型的隐式转换

355 阅读5分钟

昨天在 @木易杨说 面试题整理看到这样一道题目,觉得非常有意思,今天一大早看了大家讨论出来的答案之后,不得不佩服一些人的思路,于是自己就整理了这篇文章对其中涉及的原理进行一下梳理总结。

// 下面代码中 a 在什么情况下会打印1
var a = ?;
if(a == 1 && a == 2 && a == 3){
 	console.log(1);
}

在讨论区看到了很多答案,许多答案也值得去思考。

// 解法一: 利用 toString
let a = {
    i: 1,
    toString () {
    return a.i++
    }
}
if(a == 1 && a == 2 && a == 3) {
    console.log(1);
}
// 解法二: 利用 valueOf
let a = {
    i: 1,
    valueOf () {
    return a.i++
    }
}
if(a == 1 && a == 2 && a == 3) {
    console.log(1);
}
// 解法三: 
var a = [123];
var index = 0;
a.toString = function(){
    return this[index++]
}
if(a == 1 && a == 2 && a == 3) {
    console.log(1);
}
// 解法四: 利用数组
var a = [123];
a.join = a.shift;
if(a == 1 && a == 2 && a == 3) {
    console.log(1);
}

// 解法五: 利用 Object.defineProperty
Object.defineProperty(window'a', {
    get: function() {
        return this.value = this.value ? (this.value += 1) : 1;
    }
});

// 解法六: 利用symbol
let a = {[Symbol.toPrimitive]: ((i) => () => ++i) (0)};
if(a == 1 && a == 2 && a == 3) {
    console.log(1);
}

比较操作

在比较操作涉及到不同类型值时,会涉及到隐式转换,其中存在着很多规则需要去记忆,那我们就根据这道题目去探讨一下其中涉及到的规则。

对象 和 原始类型值 比较

如果对象和原始类型的值比较,那么对象会转换为原始类型的值,再进行比较。 在转换的时候,会首先访问对象的 valueOf 方法,若返回值是 原始值类型,则转换为该值;否则,会调用对象的 toString 方法,若返回值是 原始值类型,则转换为该值,否则报错。

也就是说,通过 valueOf 或者 toString 将对象隐式转换成原始值的时候, 会调用 valueOf ,再调用 toString ,若两个方法都不能得到原始值,则报错。

valueOf 和 toString

默认情况下,两个方法都会被每个Object对象继承,也就是说每个对象都具有这两种方法,但是我们可以通过重写方法,来覆盖该对象原始的valueOftoString方法。前两种答案就是利用这种方式实现的。

不同类型对象的valueOf()方法的返回值
对象 返回值 返回值类型
Array 返回数组对象本身。 Array
Boolean 布尔值。 boolean
Date 存储的时间是从 1970 年 1 月 1 日午夜开始计的毫秒数 UTC。 number
Function 函数本身。 Object
Number 数字值。 number
Object 对象本身。这是默认情况。 Object
String 字符串值。 string
Math 和 Error 对象没有 valueOf 方法。
// Array:返回数组对象本身
var array = ["ABC"true12-5];
console.log(array.valueOf()); // ["ABC", true, 12, -5]
console.log(array.valueOf() === array);   // true

// Date:当前时间距1970年1月1日午夜的毫秒数
var date = new Date(2013718231159230);
console.log(date.valueOf());   // 1376838719230

// Number:返回数字值
var num =  15.26540;
console.log(num.valueOf());   // 15.2654

// 布尔:返回布尔值true或false
var bool = true;
console.log(bool.valueOf());   // true

// new一个Boolean对象
var newBool = new Boolean(true);
// valueOf()返回的是true,两者的值相等
console.log(newBool.valueOf() == newBool);   // true
// 但是不全等,两者类型不相等,前者是boolean类型,后者是object类型
console.log(newBool.valueOf() === newBool);   // false

// Function:返回函数本身
function foo(){}
console.log( foo.valueOf() === foo );   // true
var foo2 =  new Function("x""y""return x + y;");
console.log( foo2.valueOf() );
/*
ƒ anonymous(x,y
) {
return x + y;
}
*/

// Object:返回对象本身
var obj = {name: "张三", age: 18};
console.log(obj.valueOf()); // {name: "张三", age: 18} 依然是对象类型
console.log( obj.valueOf() === obj );   // true

// String:返回字符串值
var str = "https://www.baidu.com/";
console.log( str.valueOf());   // https://www.bufanui.com/

// new一个字符串对象
var str2 = new String("https://www.baidu.com/");
// 两者的值相等,但不全等,因为类型不同,前者为string类型,后者为object类型
console.log( str2.valueOf() === str2 );   // false
不同类型对象toString()方法的返回值

默认情况下,toString() 返回 "[object type]",其中type是对象的类型。那也就是说,我们可以通过toString()检测对象类型。

var o = new Object();
o.toString(); // returns [object Object]

var toString = Object.prototype.toString;
toString.call(new Date); // [object Date]
toString.call(new String); // [object String]
toString.call(Math); // [object Math]
toString.call(undefined); // [object Undefined]
toString.call(null); // [object Null]

数组 和 原始类型值 比较

我们知道 数组也属于对象,应该和对象的规则一样.(见解法三)

除此以外,对于数组对象,toString 方法返回一个字符串,该字符串由数组中的每个元素的 toString() 返回值经调用 join() 方法连接(由逗号隔开)组成。 也就是说,我们可以通过修改数组的join方法,来达到目的。(解法四)

原始类型值 和 原始类型值 比较

  • 如果两个操作数都为数值,那么执行数值比较
  • 如果两个操作数都为字符串,那么比较两个字符串对应的字符串编码
  • 如果一个操作数为数值,那么将另一个操作数转换为数值,执行数值比较
  • 如果一个操作数为布尔值,那么将这个操作数转换为数值(true为1,false为0),执行数值比较

写到这里,突然发现了以下两篇文章,让我对 ToPrimitive 有了了解,才发现自己对隐式转换的理解还有待提高。原来认为这些总结记忆一下就可以了,却没想到一个对象类型的转换就这么值得深究。

对于那些这方面知识像我一样有欠缺的同学,强烈建议你们看一看上面两篇文章,一定会有所收获。