本文公布于 https://wintc.top/article/50,转载请注明
都晓得 JS 里”==“和”===“的区别,在于非同类型变量的比拟中,”===“会间接返回 false,而”==“则会先将两个比拟值先转换为同一类型,再进行比拟。然而,这里”先转换为同一类型“是什么样的规定呢?
一、容易漠视的比拟细节
始终都没有在意”比拟中的隐式类型转换“这个问题,因为常见的状况都太简略了:
"1" == 1; // true
1 == "1"; // true
0 == false; // true
很简略,很直观,直觉就是如此。直到我看见上面的比拟:
![] == [] // true
看到这个比拟前,我不知到没有非凡解决(非劫持、代理等)的 a 值能使得 a == a && a == !a 会返回 true,然而当初它就在这里:
> a = []
[]
> a == a && a == !a
true
是时候该彻底把握”比拟中的隐式类型转换“了。许多教程、书本都倡议应该应用”===“,防止应用”==“,以防止代码中的不确定性以及”===“速度会更快(因为没有类型转换)。经典书籍《你不晓得的 Javascript》一书中的观点却非如此,我比拟同意书中的观点,书中认为:存在”==“就应该搞清楚它的作用原理并且在代码中正当应用它,而不是一味避之。对我而言,我极少应用”===“,并且在编码时防止在不同类型变量之间进行比拟。
二、”==“作用规定
a == b,如果 a、b 类型雷同,那很简略,值雷同即为 true,不同即为 false。所以这里只探讨 a、b 类型不同的状况——尽管应该防止不同类型变量相比拟,然而弄明确”比拟中的隐式类型转换“却十分必要。
参照 MDN 文档梳理了一下不同类型的的值比拟的规定:
- 当数字与字符串进行比拟时,会尝试将字符串转换为数字值。
- 如果操作数之一是
Boolean
,则将布尔操作数转换为 1 或 0。 - 如果操作数之一是对象,另一个是数字或字符串,会尝试应用对象的
valueOf()
和toString()
办法将对象转换为数字或字符串。 - null == undefined 为 true,此外通常状况下 null 和 undefined 与其它对象都不相等。
能够看到,前三条规定中,都是试图转变为字符串和数字进行比拟,在比拟中,能够把布尔值当成数字。回到方才的问题,”![] == []“就比拟容易了解了,相当于 ”false == []”,有 Boolean 操作数,先转为数字,相当于比拟”0 == []“,而”[]“转为数字是 0,所以返回 true。
三、对象的 valueOf 和 toString,转换的时候到底用哪个?
Object 对象在隐式转换的时候,会尝试调用 valueOf 和 toString 函数,向字符串或者数字转换。那优先会采纳哪一个函数的值呢?
测试后发现:如果 valueOf 或者 toString 返回原始值(”String“、”Number“、”Boolean“、”null“、”undefined“),按 valueOf > toString 的优先级获得返回值,若返回值是”null“或者”undefined“,比拟返回 false,否则依据另一个比拟值转为字符串或者数字进行比拟;如果 valueOf 和 toString 均不返回原始值,则比拟操作将会报错!
const a = {}
a.valueOf = () => 1
a.toString = () => 2
console.log(a == 1, a == 2) // true, false
const b = {}
b.valueOf = () => null // 优先级高于 toString,比拟间接返回 false
b.toString = () => '1'
console.log(b == 'null', b == 1, b == '1') // false, false, false
const c = {}
c.valueOf = () => ([]) // 返回非根本值,将尝试取 toString 比拟
c.toString = () => '1'
console.log(c == 'undefined', c == '1') // false, true
const d = {}
d.valueOf = () => ([]) // 返回非根本值
d.toString = () => ([])
console.log(d == 'undefined', d == '1') // 比拟报错:不能转为原始值
很显著,依据 valueOf > toString 的优先级能够看到,objA == ‘abc’ 的比拟并不同于简略地将 objA 显式转换为字符串进行比拟,即:objA == ‘abc’ 与 String(objeA) == ‘abc’ 后果并不一定雷同(显式转换间接走 toString):
const e = {}
e.valueOf = () => 1
e.toString = () => '2'
console.log(e == 1, e == '1', String(e) == '1', String(e) == '2') // true, true, false, true
四、更高优先级的转换函数:ES6 对象的Symbol.toPrimitive 属性
除了 valueOf、toString 函数外,ES6 标准提出了 Symbol.toPrimitive 作为对象属性名,其值是一个函数,函数定义了对象转为字符串、数字等原始值的返回值,其优先级要高于 valueOf、toString。
**Symbol.toPrimitive**
是一个内置的 Symbol 值,它是作为对象的函数值属性存在的,当一个对象转换为对应的原始值时,会调用此函数。该函数被调用时,会被传递一个字符串参数 hint
,示意要转换到的原始值的预期类型。hint
参数的取值是 "number"
、"string"
和 "default"
中的任意一个。对于”==“操作符,hint 传递的值是”default“。
const a = {}
a[Symbol.toPrimitive] = (hint) => {if (hint == 'number') return 1
if (hint == 'string') return 2
return 3
}
a.valueOf = () => 4
a.toString = () => 5
console.log(a == 1, a == 2, a == 3, a == 4, a == 5) // false, false, true, false, false
如果应用 Number 或者 String 强制转换 a,则传入的”hint“会是”number“或者”string“。
五、总结
隐式类型转换的确是比拟容易疏忽的问题,毕竟通常很少用失去。本文介绍的 valueOf、toString、Symbol.toPrimitive 等函数,都能够干预到类型转换的后果。想起以前网上见到的一道面试题:如何让 a == 1 && a == 2 && a == 3 同时成立?应用上述几个函数应该很简略就能够解答这个问题。