JavaScript中的類型轉換

正文以前,先拋出幾組問題:數組

// 第一組
[] == [] //false

// 第二組
[] == ![] //true

{} == !{} //false

{} == ![] // false

[] == !{} //true

{} == 1 // false

// 第三組
{} < 1 // false

{} > 1 // false

看到這幾個問題,是否是一臉懵逼?函數

稍微有點基礎的同窗,應該一眼就能看出 [] == [] 輸出 false,由於 Object 是引用類型,兩個引用類型作 == 比較,若是它們引用的是同一個地址,輸出 true,不然輸出 false。可是後面幾道題可能會有一點點麻煩。this

後面幾道題都涉及到 JavaScript 中的一個難點:隱式轉換。本文將會帶領你們深刻了解 JavaScript 中的類型轉換機制。spa

1,JavaScript 數據類型

js數據類型分爲兩大類:prototype

  • 基本類型(原始值):Undefined,Null,Boolean,Number,String, Symbol
  • 對象類型:Object

2,ECMAScript 規範中的抽象操做

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 根據下列規則將其參數轉換爲布爾值類型的值:

  • Undefinedfalse
  • Nullfalse
  • Boolean:結果等於輸入的參數(不轉換)。
  • Number:若是參數是 +0, -0,NaN,結果爲 false ;不然結果爲 true
  • String:若是參數是空字符串(其長度爲零),結果爲 false,不然結果爲 true
  • Symboltrue
  • Objecttrue

2.3 ToNumber 轉換爲數值類型

抽象操做 ToNumber 根據下列規則將其參數轉換爲數值類型的值:

  • Undefined:NaN
  • Null:+0
  • Boolean:若是參數是 true,結果爲 1。若是參數是 false,此結果爲 +0
  • Number:結果等於輸入的參數(不轉換)。
  • String:參見下文
  • Symbol:拋出 TypeError 異常
  • Object:先進行 ToPrimitive 轉換,獲得原始值,再進行 ToNumber 轉換

2.3.1 對字符串類型應用 ToNumber

對字符串應用 ToNumber 時,若是符合以下規則,轉爲數值:

  • 十進制的字符串數值常量,可有任意位數的0在前面,如 '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 轉換爲字符串格式的給出以下所示:

  1. 若是 m 是 NaN,返回字符串 "NaN"
  2. 若是 m 是 +0-0,返回字符串 "0"
  3. 若是 m 小於零,返回 "-m"
  4. 若是 m 正無限大,返回字符串 "Infinity"。若是 m 負無限大,返回字符串 "-Infinity"
  5. 不然,返回 "m" 或 m 的指數形式的字符串數值常量

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,由三部分組成:

  • base:一個 undefined,Object, Boolean, String, Symbol, Number 或者 環境記錄(Environment Record)
  • referreference name:一個字符串 或 Symbol 值
  • strict mode flag:嚴格引用標誌

舉個例子:賦值語句 let obj.a = 1 中的 obj.a 產生的 Reference,base 是 obj,referreference name 是 'b',至於 strict mode flag 是用來檢測是否處於嚴格模式。

Reference 和 環境記錄(Environment Record) 這些概念是爲了更好地描述語言的底層行爲邏輯才存在的,並不存在於咱們實際的 js 代碼中。

4,邏輯非運算符(!)

邏輯非運算符(!) 按下列過程將表達式轉換爲布爾值

  1. expr 爲表達式求值的結果
  2. oldValue = ToBoolean(GetValue(expr))
  3. 若是 oldValue === true,返回 false;不然返回 true

所以,邏輯非運算符(!)能夠看成是:對 ToBoolean 操做的結果取反

5,== 運算符

比較運算 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

  1. 根據 上面 邏輯非運算符 和 ToPrimitive 的規則,![] 返回 false,所以,咱們接下來須要比較的是 [] == false
  2. [] == false 符合上面規則中的第 7 條,須要對 false 執行 ToNumber 轉換,獲得 0,接下來要比較 [] == 0
  3. [] == 0 符合上面規則中的第 9 條,對 [] 進行 ToPrimitive 轉換,獲得空字符串 '',接下來要比較 '' == 0
  4. '' == 0 符合上面規則中的第 5 條,對 '' 進行 ToNumber 轉換,獲得 0
  5. 接下來比較 0 == 0,獲得true

其餘幾道題我就不一一分析了,有興趣的同窗們能夠本身分析驗證。提示一下,須要注意 Object.prototype.toStringArray.prototype.toString 的區別

6,比較運算符

比較運算 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。
相關文章
相關標籤/搜索