Truth, Equality and JavaScript(译)

【原文地址】https://javascriptweblog.wordpress.com/2011/02/07/truth-equality-and-javascript/
一篇 2011 年的老文章,很赞同作者说的 you can’t master a language until you know it inside out – and fear and evasion are the enemies of knowledge. 阅读之,学习之。

先看几个老鸟也会觉得很 confuse 的例子

if( [ 0 ] ) {
    console.log( [0] == true );     // false
    console.log( !![0] );           // true
}

if( 'potato' ) {
    console.log( 'potato' == false );   // false
    console.log( 'potato' == true );    // false
}

上面的例子看来使用 JavaScript 进行判断的时候结果常常是出乎意料的。有个好消息是已经有标准文档规定这些表达式应该返回怎样的值,并且所有的浏览器都将遵循该规则进行实现。

JavaScript 中关于 truthequality 的求值主要有以下三种情况:

  • conditional statements and operators( 条件申明和操作: if, ? :, ||, && etc )
  • the equal operator( 等于操作符: == )
  • the strict equals operator( 严格等于操作符: === )

Conditional 条件声明和操作

在 JavaScript 中,所有的条件申明操作遵循相同的取值规则。下面将以 if 为例解释这个规则。
if 声明(表达式)会使用 ES5 中定义的一个抽象方法 ToBoolean 将其声明(表达式) 转换为一个布尔类型的值。(ecma-262 (6,7th edition 在 7.1.2, 5th edition 在 9.2 ))[http://www.ecma-international.org/ecma-262/7.0/index.html#sec-toboolean]

| 参数(表达式)类型 | 结果 |
|: --- :|: --- :|
| Undefined | false |
| Null | false |
| Boolean | 传入参数值(不做类型转换)|
| Number | +0, -0 和 NaN 会被当做是 false,其余值都是 true |
| String | 空字符串(长度为0) 会被当做是 false, 其余值都是 true |
| Object | 所有对象都会被当做是 true |

这是 JavaScript 所使用的判断一个值是 truthy (eg: true, 'potato', 36, [12, 34]) 或 falsey (eg: false, 0, '') 的规则。

看到这里就能明白为什么 if([0]) 会允许进入后面的代码块执行了 (数组是一个对象,对象都被按照 true 解析)。

再来看一些或许会让你觉得奇怪,但是却是符合上面描述的规则的例子:

var trutheyTester = function(expr) {
    return expr ? 'truthey' : 'falsey';
}

trutheyTester({});  // truthey( 对象都被当做true )

trutheyTester(false);   // falsey
trutheyTester(new Boolean(false));  // truthey (new Boolean(false) 返回的是对象)

trutheyTester('');  // falsey
trutheyTester(new String(''));  // truthey

trutheyTester(NaN); // falsey
trutheyTester(new Number(NaN));     // truthey

The Equals Operator(==) 等于操作符

== 型的等于操作符(以下称为非严格等于)是非常自由的(liberal)。两边的值有可能根本就会是不同的类型,但运算却有可能返回 true 的结果。因为非严格等于操作会对将要进行比较运算的一边或者两边进行强制类型转换再进行比较(通常都是装换为number型的值)。

很明显使用 == 操作符比较不同类型的值,且可能返回 true 的结果,很酷,但同时也很危险。我们的某一位 JavaScript 领域专家建议完全不要使用 == 运算符。

这种避免使用 == 的建议我并不是十分赞同,因为我认为只有深入的学习这门语言你才能算是掌握(master)了它。回避是真正掌握知识的敌人。

而且,即使我们假装 == 操作符并不存在,你也依然无法摆脱 JavaScript 中大量存在的类型装换问题。如果能够恰当地使用 == 进行比较,将会是一个很好的工具,用来创建出更简洁,优雅和可读的代码。

无论如何,让我们来看看 ECMA 是如何定义 == 运算符的标准表现的。它实际上并没有想象中那么吓人,只需要记住使用 == 比较的时候 undefined 和 null 是相等的,大多数情况下其他类型的参数会被转换成数字类型用来帮助进行更好的比较。

| type(x) | type(y) | result |
|: --- :|: --- :|: --- :|
| null | undefined | true |
| undefined | null | true |
| Number | String | x == toNumber(Y) |
| String | Number | toNumber(x) == y |
| Boolean | (any) | toNumber(x) == y |
| (any) | Boolean | x == toNumber(y) |
| String or Number | Object | x == toPrimitive(y) |
| Object | String or Number | toPrimitive(x) == y |
| (any) | (any) 和 x 类型相同 | 参考 x === y |
| 其他一些情况 | ... | false |

只要得到的结果还是一个表达式,该算法就会重新执行,直到最终返回布尔型的结果为止。
toNumbertoPrimitive 是两个内置方法,按照下面的规则对传入的参数进行转换。

toNumber

| Argument Type | Result |
|: --- :|: --- :|
| Undefined | NaN |
| Null | +0 |
| Boolean | true -> 1, false -> +0 |
| Number | 传入参数,不做转换 |
| String | 'abc' -> NaN, '123' -> 123 |
| Object | 先使用 toPrimitive 装换对象值,然后在对 toPrimitive 得到的值进行,toNumber 运算|

toPrimitive

| Argument Type | Result |
|: --- :|: --- :|
| Object | 在 比较运算中,如果 valueOf 方法返回一个原始类型值,返回这个值。如果 toString 方法返回 原始类型值,返回这个值。如果前面两个方法都不返回原始类型值,抛出一个错误 |
| otherwise ... | 不进行类型转换,直接返回传入的参数 |

学习了上面的规则,来看几个实际的例子:
[0] == true;

[0] == true;    // false

// 伪码
// 1. 布尔型 使用 toNumber 转换
[0] == 1;
// 2. 对象使用 valueOf 或 toString 获得原始类型值
'0' == 1;
// 3. string 使用 toNumber 装换
0 == 1; // false

'potato' == true;

'potato' == true;   // false

// 伪码
// 1. 布尔型 使用 toNumber 转换
'potato' == 1;
// 2. string 使用 toNumber 转换
NaN == 1;   // false

'potato' == false;

'potato' == false;  // false

// 伪码
// 1. 布尔型 使用 toNumber 转换
'potato' == 0;
// 2. string 使用 toNumber 转换

Object with valueOf

crazyNumeric = new Number(1);
crazyNumeric.toString = function() { return '2' };
crazyNumeric == 1; // true

// 伪码
// 1. 对象 使用 valueOf 或 toString 转换
// 1 == 1;  // true

Object with toString

crazyNumeric = {
    toString: function() { return '2' };
}
crazyNumeric == 1;

// 伪码
// 1. 对象 使用 valueOf 或 toString 转换
// 2 == 1;  // false

两边类型装换总是按照这样的顺序进行 Boolean > Object( valurOf > toString ) > String(译者参考后面提供的 javascript == 操作符比较流程工具 添加的内容)

The Strict Equals Operator( === ) 严格等于

这种情形会比较简单。如果是对不同类型进行比较,结果永远都会是 false。
如果比较两边是同一数据类型的将使用一个直观的规则进行比较: 对象比较,必须两边都是同一对象的引用时才会得到 true 的结果; 字符串必须包含完全相同的字符集(contain identical character sets)才会得到 true 的结果; 其他的原始类型必须值相同才能返回 true 的结果。
NaN, null, undefined 三者都不会存在一个严格相等的组合。 NaN 甚至都不等于它自身(NaN 是 JavaScript 中唯一不等于自身的值)。

一些本不需要使用 === 的场景

// unnecessary
if ( typeof myVar === 'function' ) { /* ... */ }

// better
if ( typeof myVar == 'function' ) { /* ... */ }

typeof 表达式返回一个 string 类型的值,这个操作永远都是比较两个字符串。因此 == 在这种情况下永远都能得到正确的比较结果。

// unnecessary 
var missing = ( myVar === undefined || myVar === null );

// better
var missing = ( myVar == null );

==undefinednull 彼此相等。
在某些 ES 实现中,undefined 是可以修改的, 因此和 null 进行比较更为安全。

// unnecessary 
if ( myArr.length === 3 ) { /* ... */ }

// better

if ( myArr.length == 3 ) { /* ...*/ }

都是数字的比较,== 在这种情况下永远都能得到正确的比较结果。

扩展阅读:

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,362评论 5 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,330评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,247评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,560评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,580评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,569评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,929评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,587评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,840评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,596评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,678评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,366评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,945评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,929评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,165评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,271评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,403评论 2 342

推荐阅读更多精彩内容

  • 第5章 引用类型(返回首页) 本章内容 使用对象 创建并操作数组 理解基本的JavaScript类型 使用基本类型...
    大学一百阅读 3,204评论 0 4
  • 第1章 JavaScript 简介 JavaScript 具备与浏览器窗口及其内容等几乎所有方面交互的能力。 欧洲...
    力气强阅读 1,111评论 0 0
  • 原文: https://github.com/ecomfe/spec/blob/master/javascript...
    zock阅读 3,370评论 2 36
  • FreeCodeCamp - Basic JavaScript 写在前面: 我曾经在进谷前刷过这一套题,不过当时只...
    付林恒阅读 16,428评论 5 28
  • "沒有了,沒有了,只剩下最後的五百塊!"我心里暗自着急。 這個月花錢如流水,買了手袋買了衣服還買了喜歡的化妝品,因...
    安亲阅读 1,338评论 0 0