前段时间在网上看到一道比较迷的题:js环境下,如何让(a==1 && a==2 && a==3)这个表达式返回true。
网上给出的答案为:
const a = {
i: 1,
toString: function() {
return a.i++;
}
}
if(a==1 && a==2 && a==3) {
console.log('hello')
}
或者这样:
const a = {
i: 1,
valueOf: function() {
return a.i++;
}
}
if(a==1 && a==2 && a==3) {
console.log('hello')
}
看到这里相信很多人像我一样的菜鸡有相同的疑问:
- 为什么要用toString() / valueOf()?
- 这里为什么要将a写成Object,写成其他数据类型不行吗?
- 将 == 改成 === 还能成立吗?
- 为什么要限制在JS中,其他语言不行吗?
其实这道题的考点就在于JS是如何获取一个变量的以及获取变量时做了哪些操作。
解答问题1:为什么要用toString() / valueOf()?
首先,我们先搞清楚这两个方法是干什么用的:
toString( ):返回对象的字符串表示。
valueOf( ):返回对象的字符串、数值或布尔值表示。
这两个方法一般是交由JS去隐式调用,以满足不同的运算情况。
在数值运算里,会优先调用valueOf(),在字符串运算里,会优先调用toString()。
如果对象存在任意原始值,它就默认将对象转换为表示它的原始值,如果对象是复合值,而且大多数对象无法真正表示为一个原始值,因此默认的valueOf( )方法简单地返回对象本身,而不是返回一个原始值。
//例如
//先看看toString()方法的结果
var c = true;
var d = {test:'123',example:123}
var e = function(){console.log('example');}
var f = ['test','example'];
c.toString();// "true"
d.toString();// "[object Object]"
e.toString();// "function (){console.log('example');}"
f.toString();// "test,example"
//再看看valueOf()方法的结果
var a = 3;
var b = '3';
var c = true;
var d = {test:'123',example:123}
var e = function(){console.log('example');}
var f = ['test','example'];
a.valueOf();// 3
b.valueOf();// "3"
c.valueOf();// true
d.valueOf();// {test:'123',example:123}
e.valueOf();// function(){console.log('example');}
f.valueOf();// ['test','example']
如果对象没有原始值,则valueOf()将返回对象本身。
你可以在自己的代码中使用valueOf()将内置对象转换为原始值。创建自定义对象时,可以覆盖Object.prototype.valueOf()来调用自定义方法,而不是默认Object方法。
而此题答案中
const a = {
i: 1,
valueOf: function() {
return a.i++;
}
}
if(a==1 && a==2 && a==3) {
console.log('hello')
}
正是覆盖了对象原型上的valueOf()方法,使内置对象转换为原始值return a.i++。所以当判断a==1时,因为a不是一个原始值,JS引擎就会自动调用 valueOf()方法,使对象a转换为valueOf()方法的返回值1,当判断a==2时,a.i已经完成了++操作,所以返回的是2,以此类推,所以(a==1 && a==2 && a==3) 返回true
解答问题2:这里为什么要将a写成Object?
JavaScript的数据类型:
- Underfined(没有赋值)
- Null(没有任何值,强制赋值null)
- Boolean(TRUE、FALSE)
- Number(数值型,包括整数和小数)
- String
- Object(复杂数据类型)
- function(函数类型)
原因:在js的7种数据类型种,valueOf()和toString()是Object的的原型方法。 每个对象都具有该方法,但是各对象返回的值有一定的区别。
const a = {
i: 1,
valueOf: function() {
return a.i++;
}
}
console.log(a,"a")
const b = 1
console.log(b,"b)
将输出打印到浏览器控制台可以看到,只有a上有valueOf()和toString()方法
除了上面两种写法,还可以这样写,原理都是一样(改变对象原型上的valueOf()方法,将对象转换为我们所需要的值):
let i=1
let a = new Number(1)
Number.prototype.valueOf = ()=>{
return i++
}
if(a==1 && a==2 && a==3) {
console.log('hello')
}
/***/
let b = new Number(3) //通过New Number来实例化的是一个String对象
console.log(typeof b) // object
let c = 3 //数值直接量
console.log(typeof c) //number
/**通过 Number 构造函数,构造的数值与数值直接量的类型是不同的。前者为引用型对象,后者为值类型字符串*/
解答问题3:将 == 改成 === 还能成立吗?
== 与 ===的区别:
== 是宽松下的相等,只需要值相同就能判断相等。JavaScript中存在隐式转换。等于操作符(==)在比较中会先进行类型转换,再确定操作数是否相等。
=== 是严格相等,不仅需要两个变量的值相同,类型也要相同才能相等,全等运算符不会做类型转换。
null == undefined // true
null === undefined // false
1 == '1' // true
1 === '1' // false
所以,在严格相等的情况下,上面的答案不成立。
解答问题4:为什么要限制在JS中,其他语言不行吗?
这里我只探讨JAVA:
在java中 == 是直接比较的两个对象的堆内存地址,如果相等,则说明这两个引用实际是指向同一个对象地址的。
对于java基本数据类型(byte,short,char,int,float,double,long,boolean)来说,他们是作为常量在方法区中的常量池里面以HashSet策略存储起来的,对于这样的字符串 "123" 也是相同的道理,在常量池中,一个常量只会对应一个地址,因此不管是再多的 123,"123" 这样的数据都只会存储一个地址,所以所有他们的引用都是指向的同一块地址。
int a = 123;
int b = 123;
System.out.println(a == b) //true
String s1 = "123";
String s2 = "123";
System.out.println(s1 == s2) //true
String s3 = new String("123")
System.out.println(s1 == s3) //false
另外,对于基本数据的包装类型(Byte, Short, Character,Integer,Float, Double,Long, Boolean)除了Float和Double之外,其他的六种都是实现了常量池的,因此对于这些数据类型而言,一般我们也可以直接通过==来判断是否相等。
Integer a = 127;
Integer b = 127;
System.out.println(a == b) //true
Integer m = 128;
Integer n = 128;
System.out.println(m == n) //false
结果是 true,false。因为 Integer 在常量池中的存储范围为[-128,127],127在这范围内,因此是直接存储于常量池的,而128不在这范围内,所以会在堆内存中创建一个新的对象来保存这个值,所以m,n分别指向了两个不同的对象地址,故而导致了不相等。
写到这,显而易见在java中,(a==1 && a==2 && a==3)不可能同时成立,因为一个地址不可能同时等于三个不同的地址。