昨天在 @木易杨说 面试题整理看到这样一道题目,觉得非常有意思,今天一大早看了大家讨论出来的答案之后,不得不佩服一些人的思路,于是自己就整理了这篇文章对其中涉及的原理进行一下梳理总结。
// 下面代码中 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 = [1,2,3];
var index = 0;
a.toString = function(){
return this[index++]
}
if(a == 1 && a == 2 && a == 3) {
console.log(1);
}
// 解法四: 利用数组
var a = [1,2,3];
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对象继承,也就是说每个对象都具有这两种方法,但是我们可以通过重写方法,来覆盖该对象原始的valueOf
和 toString
方法。前两种答案就是利用这种方式实现的。
不同类型对象的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", true, 12, -5];
console.log(array.valueOf()); // ["ABC", true, 12, -5]
console.log(array.valueOf() === array); // true
// Date:当前时间距1970年1月1日午夜的毫秒数
var date = new Date(2013, 7, 18, 23, 11, 59, 230);
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 有了了解,才发现自己对隐式转换的理解还有待提高。原来认为这些总结记忆一下就可以了,却没想到一个对象类型的转换就这么值得深究。
对于那些这方面知识像我一样有欠缺的同学,强烈建议你们看一看上面两篇文章,一定会有所收获。