JS基礎篇之強制類型轉換與操做符

也許你們會以爲js強制類型轉換與操做符知道個大概就夠了。是的,對於搬磚工來講,夠了。可是做爲一個有追求的前端,我深信只有掌握了別人不肯意掌握的東西,才能讓本身變得更強大更有競爭力。

小綱老師

類型轉換規則

或許你們不喜歡隱式類型轉換,以爲這東西太沒人性。可是你有沒有想過,這也許正是js語言的獨特之處?我認同kyle大佬說的,若是你完全掌握了隱式類型轉換,那麼對你來講,它就是「顯式」類型轉換了。

之因此先講類型轉換,是由於在操做符運算中涉及了大量的隱式類型轉換。javascript

0. ToPrimitive

抽象操做ToPrimitive用於將引用類型轉爲原始類型。實現細節比較複雜,有興趣的童鞋能夠參考這裏前端

//模擬一個對象的轉基本類型操做 ToPrimitive
var o = {};
o[Symbol.toPrimitive] = function(hint) {
  console.log(hint) //hint字符串至爲 string number default 中的一個
  if (hint == "default" || hint == "number") {
    if (o.valueOf && typeof(o.valueof()) != 'object') {
      return o.valueOf()
    } else {
      return o.toString()
    }
  } else {
    if (o.toString && typeof(o.toString()) != 'object') {
      return o.toString()
    } else {
      return o.valueOf()
    }  
  }
}
String(o) // string
Number(o) // number
1+o // default
1-o // number
o++ // number
++o // number

規則以下:java

  1. 若是傳入參數是string(目前只有調用String()函數是執行這個順序):首先檢查該值是否有toString()方法。若是有而且返回基本類型值,就使用該值進行強制類型轉換。若是沒有就檢查該值是否有valueOf()方法。若是有而且返回基本類型值就使用該回值來進行強制類型轉換,若是沒有或者返回的不是基本類型值,就拋出錯誤。
  2. 若是傳入參數是number/default(常見強制類型轉換都是這個順序):首先檢查該值是否有valueOf()方法。若是有而且返回基本類型值,就使用該值進行強制類型轉換。若是沒有就檢查該值是否有toString()方法。若是有而且返回基本類型值就使用該回值來進行強制類型轉換,若是沒有或者返回的不是基本類型值,就拋出錯誤。

1. ToString

抽象操做 ToString,負責處理非字符串到字符串的強制類型轉換。當須要一個值的字符串形式,就會進行 ToString 類型轉換。segmentfault

String()函數就會執行抽象操做 ToString,遵循下列轉換規則:數組

  1. 若是值是基本類型,則直接轉爲字符串。若是是引用類型,則執行ToPrimitive抽象操做;
  2. 若是值是 null,則返回"null";
  3. 若是值是 undefined,則返回"undefined"。
String()  // ''
String(0)   // '0'
String(true)  // 'true'
String(null)  // 'null'
String(undefined)  // 'undefined'
String(Symbol('asdf'))  // "Symbol('asdf')"
String({})  // '[Object object]'
// 數組的默認 toString() 方法通過了從新定義,將全部單元字符串化之後再用 "," 鏈接起來
String([])  // ''
String([1,2,3,4,'asdf'])  // '1,2,3,4,asdf'

2. ToNumber

抽象操做 ToNumber,負責處理非數字到數字的強制類型轉換。函數

Number()執行抽象操做 ToNumber,函數的轉換規則以下。學習

  1. 若是是 Boolean 值,true 和 false 將分別被轉換爲 1 和 0。
  2. 若是是數字值,只是簡單的傳入和返回。
  3. 若是是 null 值,返回 0。
  4. 若是是 undefined,返回 NaN。
  5. 若是是字符串:若是字符串是空的(不包含任何字符),則將其轉換爲0;若是含非數字,則將其轉換爲 NaN。
  6. 若是是對象,則執行ToPrimitive抽象操做,返回基本類型再按照以上規則處理。
Number()  // 0
Number('')  // 0
Number(' ')  // 0
Number('0')  // 0
Number('asdf')  // NaN
Number(true)  // 1
Number(false)  // 0
Number(null)  // 0
Number(undefined)  // NaN    與null不一樣,須要注意
// 對象會先經過抽象操做ToPrimitive轉爲基本類型,而後再轉數字
Number({})  // NaN
Number([])  // 0
Number([''])  // 0
Number([' '])  // 0
Number(['0'])  // 0
Number([1,2])  // NaN

3. ToBoolean

抽象操做 ToBoolean,負責處理非布爾值到布爾值的強制類型轉換。編碼

轉換爲 boolean 類型是最爲簡單的一個。轉換規則以下:code

(1) 能夠被強制類型轉換爲 false 的值對象

  • undefined
  • null
  • false
  • +0、-0 和 NaN
  • ""

(2) 其餘值會被被強制類型轉換爲 true

這裏有一個概念須要先理解:js的操做符和操做數組成了表達式,表達式一定會返回一個值。不管是一元操做 ++a,仍是布爾操做 [] || false,都會返回一個值。另外關於js運算符優先級請參閱MDN的: 運算符優先級

一元操做符

// 假設存在變量a
+a // 一元加操做符
-a // 一元減操做符
++a // 前置遞增操做符
--a // 前置遞減操做符
a++ // 後置遞增操做符
a-- // 後置遞減操做符

一元操做符指的是隻能操做一個值的操做符,區別與加性操做符能夠操做兩個值(如a+b)。

1. 一元加減操做符

一元加操做符+用於非數字的強制類型轉換,做用等同於Number()。如:

+'1.1' // 1.1
+'asdf' // NaN
+true // 1
+false // 0
+null // 0
+undefined // NaN
+{} // NaN
+[] // 0
+new Date() // 1556258367546

一元減操做符-行爲與+相似,只不過最終運算結果是負數。如-true結果是-1

2. 遞增遞減操做符

不一樣於一元加減操做符,遞增遞減操做符只能做用於number類型。若用於其餘類型會直接拋錯。

//前置遞增
var a = 57;
var b = ++a;
console.log(b); // 58
//後置遞增
var a = 57;
var b = a++;
console.log(b); // 57

前置遞增和後置遞增的區別在於,前置遞增++a的返回值是增長1的,然後置遞增a++的返回值是不增長的。
遞減和遞增規則同樣,再也不廢話。

加性操做符

1. 加法操做符+

+ 操做符經常使用於數學的計算和字符串的拼接,規則以下:

  1. 若是兩個操做符都是數值,執行常規的加法計算
  2. 若是有一個操做數是 NaN,則結果是 NaN;
  3. 若是兩個操做數都是字符串,則將第二個操做數與第一個操做數拼接起來;
  4. 若是隻有一個操做數是字符串,則將另外一個操做數轉換爲字符串,而後再將兩個字符串拼接起來。
  5. 若是有一個操做數是對象,則執行抽象操做ToPrimitive(先valueOf再toString)取的返回值,而後再應用前面關於字符串的規則。
  6. 對於 undefined 和 null,則分別調用 String()函數並取得字符串"undefined"和"null"。
1+1 // 2
NaN+1 // NaN
'asdf'+'ghjk' // 'asdfghjk'
1+1+'1' // '21'
[]+1 // 1
null+undefined // 'nullundefined'
[]+{}+1 // '[Object object]1'   實際執行:''+'[Object object]'+1
{}+[]+1 // 1    {}位於行首會被解釋爲代碼塊,此處代碼塊被忽略,所以實際執行:+[]+1,結果爲數字1

2. 減法操做符-

  1. 若是兩個操做符都是數值,執行常規的加法計算
  2. 若是有一個操做數是 NaN,則結果是 NaN;
  3. 若是有一個操做數是字符串、布爾值、null或undefined,則先在後臺調用Number()函數將其轉換爲數值,而後再根據前面的規則執行減法計算。若是轉換的結果是 NaN,則減法的結果就是 NaN;
  4. 若是有一個操做數是對象,則執行抽象操做ToPrimitive,先調用對象的valueOf()方法以取得表示該對象的數值。若是獲得的值是NaN,則減法的結果就是NaN。若是對象沒有 valueOf()方法或者返回的不是基本類型值,則調用其 toString()方法並將獲得的字符串轉換爲數值。
1-1 // 0
NaN-1 // NaN
10-true-null // 9
10-true-undefined // NaN
[]-1 // 0
['11']-11 // 0
11-{} // NaN

乘性操做符

乘性操做符包括乘法*、除法/、除餘(求模)%。規則以下:

  1. 若是兩個操做符都是數值,執行常規乘除求模;
  2. 若是有一個操做數是 NaN,則結果是 NaN;
  3. 若是有一個操做數不是數值,則在後臺調用 Number()將其轉換爲數值,而後再應用上面的規則。

數值計算較爲特殊的以下:

Infinity*0 // NaN
Infinity/Infinity // NaN
0/0 // NaN
Infinity%a // NaN a爲任意數值
a%0 // NaN a爲任意數值

布爾操做符

1. 邏輯非 !

邏輯非操做符會將任意值轉換爲一個布爾值,轉換規則和Boolean()函數相反。連續使用兩個邏輯非操做符,等同於調用了Boolean()。常見有大牛寫代碼用!!isTrue來代替Boolean(isTrue)函數。

!undefined // true
!!undefined // false
!NaN // true
!!NaN // false
!1234 // false
!!1234 // true
!'' // true
!!'' // false

2. 邏輯或 ||

短路操做:若是第一個操做數可以決定結果,那麼就不會再對第二個操做數求值。

邏輯或操做符是短路操做,若是第一個操做數的求值結果(布爾求值,下同)爲true,則直接返回第一個操做數,再也不對第二個操做數求值。若是第一個操做符求職結果爲false,則返回第二個操做數。所以,常見大神寫代碼isExist || getIsExist(),就是利用的短路操做,若是isExist求值結果爲true,就再也不執行getExist()函數。

[] || 0 // []   對象(包括數組、函數等)的求值結果永遠爲`true`,直接返回這個對象
0 || [] // []
1 || [] // 1
NaN || 0 // 0

3. 邏輯與 &&

邏輯與操做屬於短路操做,即若是第一個操做數求值結果爲false,則直接返回第一個操做數,那麼就不會再對第二個操做數求值。若是第一個操做數求值爲true,則返回第二個操做數。能夠用來作條件限制obj && obj.value。只有obj對象存在了,纔會取obj.value值。

0 && true // 0
null && [] // null
NaN && null // NaN
[] && {} // {}

須要注意布爾操做符存在優先級:! > && > ||

null || !2 || 3 && 4 // ??????你知道結果嗎?實際上,代碼至關於下面一行
null || (!2) || (3 && 4) // 4

相等操做符

相等操做符有== != === !==四個,其中相等和不相等實行先轉換類型再比較,全等和不全等實行僅比較而不轉換類型。相等操做符返回布爾值truefalse

1. 相等和不相等

不一樣類型操做數比較規則以下:

  • 先判斷是否在對比 null 和 undefined,是的話就會返回 true。null和undefined不相等於其餘任何值。
  • 判斷二者類型是否爲 string 和 number,是的話就會將字符串轉換爲 number;
  • 判斷其中一方是否爲 boolean,是的話就會把 boolean 轉爲 number 再進行判斷;
  • 判斷其中一方是否爲 object 且另外一方爲 string、number 或者 symbol,是的話就會把 object 轉爲原始類型再進行判斷。
[] == ![] // true
/* 首先,布爾操做符!優先級更高,因此被轉變爲:[] == false
 * 其次,操做數存在布爾值false,將布爾值轉爲數字:[] == 0
 * 再次,操做數[]是對象,轉爲原始類型(先調用valueOf(),獲得的仍是[],再調用toString(),獲得空字符串''):'' == 0
 * 最後,字符串和數字比較,轉爲數字:0 == 0
*/
NaN == NaN // false     NaN不等於任何值
null == undefined // true
null == 0 // false
undefined == 0 // false

全等和不全等

全等和不全等在比較以前不轉換類型,因此相對簡單:

null === undefined // false
'1' === 1 // false
0 === false // false
[] === [] // false    引用類型比較相等性還要看是否指向同一個內存地址
NaN === NaN // false    NaN比較特殊,不等於自身

關係操做符

關係操做符小於(<)、大於(>)、小於等於(<=)和大於等於(>=)比較兩個值的大小,返回一個布爾值。當有一個操做數是非數值時,就會發生類型轉換:

  1. 若是兩個操做數都是數值,則執行數值比較。
  2. 若是兩個操做數都是字符串,則比較兩個字符串對應的字符編碼值。
  3. 若是一個操做數是數值,則將另外一個操做數轉換爲一個數值,而後執行數值比較。
  4. 若是一個操做數是對象,則執行ToPrimitive轉爲基本類型(先valueOf再toString)。
  5. 若是一個操做數是布爾值,則先將其轉換爲數值,而後再執行比較。
'23' <'3' // true    比較的是字符編碼值
'23' < 3 // false    執行規則3
NaN > 0 // false     NaN比較總會返回false
null >= 0 // true    執行規則3,注意null相等性比較和關係比較不同
undefined >= 0  //false    undefined執行關係比較會轉化爲NaN,老是返回false

條件操做符

1. 條件操做符

三元表達式就是由條件操做符? :組成:

a > b ? a : b;  // 若是 ? 前的操做求值爲 true ,則返回 a ,不然返回 b

2. 賦值操做符

js中等號 = 用於賦值操做,var a = 1就是把值1賦值給變量a。能夠和
+ - * / % 構成複合賦值:

a += b // 等同於 a = a + b
a -= b // 等同於 a = a - b
a *= b // 等同於 a = a * b
a /= b // 等同於 a = a / b
a %= b // 等同於 a = a % b

3. 逗號操做符

逗號操做符經常使用於一條語句聲明多個變量:var a = 1, b = 2, c;

4. 位操做符

js中數值是以64位格式儲存的,前32位是整數,後32位是小數。位操做符會將64位的值轉爲32位的,因此位操做符會強制將浮點數轉爲整數。下面說幾個經常使用的位操做符:

  1. 位操做符 ~~x至關於-(x+1),能夠用來代替indexOf做爲判斷條件。~str.indexOf('asdf')至關於str.indexOf('asdf')>-1
  2. 位操做符 |:可用於將值截除爲一個 32 位整數。1.11 | 0執行結果是1

總結

js的類型轉換雖然很讓人頭疼,但並非無跡可尋。只要掌握了規則,就可以按規則判斷出來類型到底會如何轉換。而規則中很重要的一部分是,引用類型到基本類型的轉換是經過ToPrimitive抽象操做完成的。掌握了ToPrimitive抽象操做,就掌握了類型轉換的核心規則。

類型轉換是很常見和很經常使用的,雖然規則多了點,但卻值得去努力學習。

相關文章
相關標籤/搜索