正文以前,先拋出幾組問題:數組
// 第一組 [] == [] //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。