正文以前,先抛出几组问题:数组
// 第一组 [] == [] //false // 第二组 [] == ![] //true {} == !{} //false {} == ![] // false [] == !{} //true {} == 1 // false // 第三组 {} < 1 // false {} > 1 // false
看到这几个问题,是否是一脸懵逼?函数
稍微有点基础的同窗,应该一眼就能看出 [] == []
输出 false
,由于 Object
是引用类型,两个引用类型作 ==
比较,若是它们引用的是同一个地址,输出 true
,不然输出 false
。可是后面几道题可能会有一点点麻烦。this
后面几道题都涉及到 JavaScript
中的一个难点:隐式转换。本文将会带领你们深刻了解 JavaScript
中的类型转换机制。spa
js数据类型分为两大类:prototype
Undefined,Null,Boolean,Number,String, Symbol
;Object
;2.1 ToPrimitive ( input [, PreferredType] ) 转换为原始值code
抽象操做 ToPrimitive(input[, PreferredType])
,将input
参数转换为一个非对象类型的值,即原始值类型。转换规则以下:对象
Undefined
:返回原始值,不转换Null
:返回原始值,不转换Boolean
:返回原始值,不转换Number
:返回原始值,不转换String
:返回原始值,不转换Symbol
:返回原始值,不转换Object
:见下文当 Type(input)
为 Object
时,能够将抽象操做 ToPrimitive(input[, PreferredType])
的执行过程用以下代码解释:blog
// 仿抽象操做 ToPrimitive(input[, PreferredType]) 的执行过程 function ToPrimitive(input: Object, PreferredType: undefined | 'String' | 'Number') { let hint; // 若是没有送 PreferredType ,hint 为 'default' // 若是 PreferredType 为 'String', hint 为 'string' // 若是 PreferredType 为 'Number', hint 为 'number' if(!PreferredType) { hint = 'default'; } else if (PreferredType === 'String') { hint = 'string'; } else if(PreferredType === 'Number') { hint = 'number'; } // 获取对象的 @@toPrimitive 方法,若是对象自身没有,会一直顺着原型链查找 let exoticToPrim = GetMethod(input, Symbol.toPrimitive); // 若是该方法不为undefined,用对象调用该方法,赋值给result // 若是 result 不是 Object 类型,返回 result;不然抛出 TypeError 异常 if(exoticToPrim !== undefined) { let result = exoticToPrim.call(input, hint); if (typeof result !== 'object') { return result; } throw new TypeError(); } // 若是该方法为undefined // 若是 hint 为 'default',令 hint 为 'number' // 返回 抽象操做 OrdinaryToPrimitive 的调用结果 if(hint === 'default'){ hint = 'number'; } return OrdinaryToPrimitive(input,hint); }
这里涉及到另外一个抽象操做 GetMethod,咱们先看一下ECMAScript 6 规范中对 GetMethod 的定义:排序
简单说来就是: 抽象操做 GetMethod(O,P)
获取对象 O 的 P 属性,若是 该属性是 undefined
,返回 undefined
;若是该属性是一个函数,返回此函数;不然抛出一个 TypeError
异常。图片
下面咱们看一下 抽象操做 OrdinaryToPrimitive
的过程:
// 仿抽象操做 OrdinaryToPrimitive(O, hint) 的执行过程 function OrdinaryToPrimitive(O: Object, hint: 'string' | 'number') { // 假定 hint 是一个字符串,而且其值只能是'string' 或 'number' // 若是 hint === 'string',令 methodNames = ['toString', 'valueOf'] // 若是 hint === 'number',令 methodNames = ['valueOf', 'toString'] let methodNames; if(hint === 'string') { methodNames = ['toString', 'valueOf']; } else { methodNames = ['valueOf', 'toString']; } // 遍历 methodNames,获取对象的方法,赋值给 result // 若是 result 不是 Object,终止遍历,并返回 result // 抛出一个 TypeError 异常 for (let item of methodNames) { let method = O[item]; let result = method.call(O); if(typeof(result) !== 'object') { return result; } } throw new TypeError(); }
Date
对象和 Symbol
对象的原型上已经部署了 [@@toPrimitive]
方法,这个方法是不可枚举(enumerable: false
),不可改写的(writable: false
)。对于Date
对象原型上的[@@toPrimitive]
方法,若是没有送hint
,会将hint
看成'string'
。
咱们可使用 Symbol.toPrimitive
来给 Object
添加 [@@toPrimitive]
方法:
Object.prototype[Symbol.toPrimitive] = function(hint) { if(hint === 'default') { let thisType = Object.prototype.toString.call(this); if(thisType === '[object Date]') { hint = 'string'; } else { hint = 'number'; } } let methodNames; if(hint === 'string') { methodNames = ['toString', 'valueOf']; } else if(hint === 'number') { methodNames = ['valueOf', 'toString']; } else { throw new TypeError('Invalid hint: ' + hint); } for (let key of methodNames) { let method = this[key]; let result = method.call(this); if(typeof(result) !== 'object') { return result; } } throw new TypeError(); }
2.1.1 对象的 valueOf() 方法和 toString() 方法
对象在执行 ToPrimitive 转换时,须要用到对象的valueOf()
和toString()
方法。咱们能够在Object.prototype
上找到这两个方法。在JavaScript
中,Object.prototype
是全部对象原型链的顶层原型,所以,任何对象都有valueOf()
和toString()
方法。
JavaScript
的许多内置对象都重写了这两个方法,以实现更适合自身的功能须要。
不一样类型对象的valueOf()
方法的返回值:
Array
: 返回数组对象自己。Boolean
: 布尔值。Date
: 存储的时间是从 1970 年 1 月 1 日午夜开始计的毫秒数 UTC。Function
: 函数自己。Number
: 数字值。Object
: 对象自己。这是默认状况。String
: 字符串值。Symbol
:Symbol值自己。不一样类型对象的toString()
方法的返回值:
Array
:链接数组并返回一个字符串,其中包含用逗号分隔的每一个数组元素。Boolean
:返回字符串 "true"
或 "false"
。Date
:返回一个美式英语日期格式的字符串。Function
:返回一个字符串,其中包含用于定义函数的源文本段。Number
: 返回指定 Number
对象的字符串表示形式。Object
: 返回 "[object type]"
,其中 type
是对象的类型。String
: 字符串值。Symbol
:返回当前 Symbol
对象的字符串表示。2.2 ToBoolean 转换为布尔值类型
抽象操做 ToBoolean
根据下列规则将其参数转换为布尔值类型的值:
Undefined
:false
Null
:false
Boolean
:结果等于输入的参数(不转换)。Number
:若是参数是 +0, -0,
或 NaN
,结果为 false
;不然结果为 true
。String
:若是参数是空字符串(其长度为零),结果为 false
,不然结果为 true
。Symbol
:true
。Object
:true
。2.3 ToNumber 转换为数值类型
抽象操做 ToNumber
根据下列规则将其参数转换为数值类型的值:
Undefined
:NaNNull
:+0Boolean
:若是参数是 true,结果为 1
。若是参数是 false,此结果为 +0
。Number
:结果等于输入的参数(不转换)。String
:参见下文Symbol
:抛出 TypeError
异常Object
:先进行 ToPrimitive
转换,获得原始值,再进行 ToNumber
转换2.3.1 对字符串类型应用 ToNumber
对字符串应用 ToNumber
时,若是符合以下规则,转为数值:
'000123'
和 '123'
都会被转为 123
'1e2'
转为 100
'-100', '-1e2'
都会转为 -100
'0b11', '0o11', '0x11'
分别转为 3, 9, 17
' 0b11 '
转为 3
0
若是字符串不符合上述规则,将转为NaN
。
2.4 ToString 转为字符串类型
抽象操做 ToString
根据下列规则将其参数转换为字符串类型的值:
Undefined
:"undefined"
Null
:"null"
Boolean
:若是参数是 true
,那么结果为 "true"
。 若是参数是 false
,那么结果为 "false"
。String
:结果等于输入的参数(不转换)。Number
:参见下文。Symbol
:抛出 TypeError
异常Object
:先进行 ToPrimitive
转换,hint
为 'string'
,获得原始值,再进行 ToString
转换2.4.1 对数值类型应用 ToString
抽象操做 ToString
运算符将数字 m 转换为字符串格式的给出以下所示:
NaN
,返回字符串 "NaN"
。+0
或 -0
,返回字符串 "0"
。"-m"
。"Infinity"
。若是 m 负无限大,返回字符串 "-Infinity"
。2.5,抽象操做 GetValue
先看一下 ECMAScript 规范中定义的 GetValue
方法:
注意区分这一句:2. If Type(V) is not Reference, return V.
中的 Reference
和咱们平时说的 引用类型
的区别。
咱们平时说的 引用类型 指的是 ECMAScript 规范中 语言类型的 Object 类型(例如 Object, Array, Date 等);而这里的 Reference 指的是ECMAScript 规范中 规范类型的 Reference 类型 ,是一个抽象的概念。
按规范的描述,Reference 是一个 name binding,由三部分组成:
举个例子:赋值语句 let obj.a = 1
中的 obj.a
产生的 Reference,base 是 obj,referreference name 是 'b',至于 strict mode flag 是用来检测是否处于严格模式。
Reference 和 环境记录(Environment Record) 这些概念是为了更好地描述语言的底层行为逻辑才存在的,并不存在于咱们实际的 js 代码中。
逻辑非运算符(!) 按下列过程将表达式转换为布尔值
expr
为表达式求值的结果oldValue = ToBoolean(GetValue(expr))
oldValue === true
,返回 false
;不然返回 true
所以,逻辑非运算符(!)能够看成是:对 ToBoolean 操做的结果取反。
比较运算 x==y,按以下规则进行:
1,若 Type(x) 与 Type(y) 相同, 则 1) 若 Type(x) 为 Undefined, 返回 true。 2) 若 Type(x) 为 Null, 返回 true。 3) 若 Type(x) 为 Number, 则 (1)、若 x 为 NaN, 返回 false。 (2)、若 y 为 NaN, 返回 false。 (3)、若 x 与 y 为相等数值, 返回 true。 (4)、若 x 为 +0 且 y 为 −0, 返回 true。 (5)、若 x 为 −0 且 y 为 +0, 返回 true。 (6)、返回 false。 4) 若 Type(x) 为 String,则当 x 和 y 为彻底相同的字符序列时返回 true。 不然,返回 false。 5) 若 Type(x) 为 Boolean,当 x 和 y 为同为 true 或者同为 false 时返回 true。 不然, 返回 false。 6) 若 Type(x) 为 Symbol,若是 x 和 y 是同一个 Symbol,返回true。不然,返回 false。 7) 若 Type(x) 为 Object,当 x 和 y 是对同一对象的引用时返回 true。不然,返回 false。 2,若 x 为 null 且 y 为 undefined,返回 true。 3,若 x 为 undefined 且 y 为 null,返回 true。 4,若 Type(x) 为 Number 且 Type(y) 为 String,返回 x == ToNumber(y)的结果。 5,若 Type(x) 为 String 且 Type(y) 为 Number,返回 ToNumber(x) == y的结果。 6,若 Type(x) 为 Boolean, 返回 ToNumber(x) == y 的结果。 7,若 Type(y) 为 Boolean, 返回 x == ToNumber(y) 的结果。 八、若 Type(x) 为 String 或 Number 或 Symbol,且 Type(y) 为 Object,返回 x == ToPrimitive(y) 的结果。 九、若 Type(x) 为 Object 且 Type(y) 为 String 或 Number 或 Symbol, 返回 ToPrimitive(x) == y 的结果。 十、返回 false。
如今,咱们来分析一下文章开头提出的问题:[] == ![] // true
ToPrimitive
的规则,![]
返回 false
,所以,咱们接下来须要比较的是 [] == false
;[] == false
符合上面规则中的第 7 条,须要对 false
执行 ToNumber
转换,获得 0,接下来要比较 [] == 0
;[] == 0
符合上面规则中的第 9 条,对 []
进行 ToPrimitive
转换,获得空字符串 ''
,接下来要比较 '' == 0
;'' == 0
符合上面规则中的第 5 条,对 ''
进行 ToNumber
转换,获得 00 == 0
,获得true其余几道题我就不一一分析了,有兴趣的同窗们能够本身分析验证。提示一下,须要注意 Object.prototype.toString
和 Array.prototype.toString
的区别
比较运算 x < y,按照以下规则执行
1,令 px = ToPrimitive(x),令 py = ToPrimitive(y)。 2,若是 Type(px) 和 Type(py) 都是 String,则 1)、若是 py 是 px 的前缀,返回 false。 2)、若是 px 是 py 的前缀,返回 true。 3)、找出 px 和 py 中 相同下标处第一个不一样的字符串单元,将其 词典排序 分别记为 m 和 n。 4)、若是 m < n,返回 true,不然,返回 false。 3,令 nx = ToNumber(px),令 ny = ToNumber(py) 1)、若是 Type(nx) 是 NaN,返回 false。 2)、若是 Type(ny) 是 NaN,返回 false。 3)、若是 nx 是 +0,ny 是 -0,返回 true。 4)、若是 nx 是 -0,ny 是 +0,返回 true。 5)、若是 nx 是 +Infinity,返回 false。 6)、若是 ny 是 +Infinity,返回 true 。 7)、若是 ny 是 -Infinity,返回 false。 8)、若是 nx 是 -Infinity,返回 true。 9)、若是 nx 的数学值小于 ny 的数学值,返回 true,不然返回 false。