关于js中alert([]==![])结果为true的讨论

By zhujinliang. Filed in 软折腾  |   
标签:, , ,
Home  

早上逛德问,发现一个有趣的问题:alert([]==![])结果为true,求解释。

一看到这问题,就知道肯定是隐式转换问题。

编译器碰到这个问题,第一步肯定是将双等号两边都变成基本变量。

左边是[],属object型变量,再看右边,是![],需要先运算一下。

![]即将[]隐式转换为boolean,然后取反,变量转boolean遵循以下规则:false、""、0、NaN、null、undefined转换为false,其余的均转换为true,故[]转换为boolean后为true,![]为false。

现在就是判断 []==false。两边类型不一样,需要做隐式转换。

上网搜索,查找javascript对于判断相等时的隐式转换规则。得到一条:

如果一个操作值是对象,另一个不是,则调用对象的valueOf()方法,得到的结果按照前面的规则进行比较。

再查Array对象的valueOf,实质就是:数组的元素被转换为字符串,这些字符串由逗号分隔,连接在一起。其操作与 Array.toString 和 Array.join 方法相同。(实际上查到的资料有错误,后面详细说明)

空Array返回结果自然就是空文本。

于是就变成了判断""==false。

此处我开始理解为将空文本转换为false,于是false==false=true;但实际是错的,这也是后话了。

同时,我给出一个代码,印证其正确性。

var test = [];
alert(test == ![]); //结果为true

var test = [];
test.valueOf = function(){
	return true;
}
alert(test == ![]); //结果为false

———————————

我的答案发表后,冯义军指出一个错误:[].valueOf()实际为真。同时给出了他的看法,简单说就是调用的是object的toString方法,而不是valueOf方法。

我通过chrome的console.debug测试,[].valueOf()返回的实质是Array对象本身,网上的所谓的与Array.toString相同是错误的。但有一点,上面的验证代码确实是有效果的。

我用了以下代码进行测试,逐个进行屏蔽测试,发现valueOf与toString两个都调用过。

//Array.prototype.toString = function(){
//	alert('called toString');
//}
//Array.prototype.valueOf = function(){
//	alert('called valueOf');
//}
alert([]==false);

然后我猜测javascript处理时,先尝试用valueOf,发现得到结果仍是object型,于是采用toString结果。又进行了其他测试,基本可确定此观点。

冯义军也赞同这个意见。

同时注意到,冯义军认为进行""==false判断的过程是:(""==false) -> ("" == 0) -> (0 == 0) –>true 。当时我觉得他的也在理,我的也不能证明错误,且无法测试。

—————————–

而后,我发现一个问题:

alert(new Date(0)==0); //或==false,结果是false
alert(new Date(0).valueOf()); //0
alert(typeof new Date(0).valueOf()); //number

我们讨论出的理论,对于Date型却不成立。难道Date型个别不成,我认为同作为object,没理由Date型与众不同。

带着疑问,我去查阅了EcmaScript的文档,Ecma-262.pdf,英文的。找到了真正的答案。

①11.9.3节讲到x==y的处理,即允许隐式转换的相等判断。里面列出了9条规则。

对于[]==false,首先符合的规则是第7条:如果y是boolean型(x任意),则进一步判断x= =ToNumber(y)。

于是成了 []==0。再次进行判断,此时又符合第9条规则,x为object型,y为string型或number型,则进一步判断ToPrimitive(x)==y。

②再看ToPrimitive函数,9.1节给出ToPrimitive函数原理,它有一个参数PreferredType

ToPrimitive对于Undifined,Null,Boolean,Number,String均是没有转换,直接返回。而对于Object型,又调用[[DefaultValue]]进行转换,同时将参数PreferredType传给[[DefaultValue]]函数。

③继续看[[DefaultValue]]函数,在8.12.8节。该函数有一个参数hint,其原理是:

如果hint为String,则先尝试调用该object的toString方法,如果无该方法,或该方法不可被执行(非function类型),或该方法返回的不是基本类型(js基本类型:Null,Boolean,String,Number),则尝试调用该object的valueOf方法,同样如果无该方法,不可执行,返回值不为基本类型,则抛出TypeError异常。toString或valueOf返回值合适的话,就作为函数的返回值返回。

如果hint为Number,则先尝试调用valueOf方法,后尝试toString方法。

如果hint为空,默认用Number模式,特别的,对于Date型object,采用String模式。这里就是判断时Date型被特殊关照的原因了。

对于[],该函数会这样处理:执行[].valueOf(),得到结果是[],不是基本类型,执行[].toString,得到结果是空文本,返回空文本。

对于(new Date(0)),该函数会这样处理:执行[].toString,得到结果是时间文本,直接返回该文本。后面判断是否等于0实质就是判断NaN是否等于0,结果为false(规则1.c.i)。

至此,[]==![]问题经过一步步演变:[]==false –> []==0 –> "" == 0

再根据规则5,x为string型,y为number型的情况,进一步判断ToNumber(x)==y

又变成了0==0,终于天下太平了。

PS. 其实9个规则中,有些是x与y对调的关系。

2 Comments

发表评论

电子邮件地址不会被公开。 必填项已用 * 标注

*