javascript
做爲一門動態類型的腳本語言,咱們在編寫js代碼的時候,每每會忽略變量的屬性類型,這在大多數狀況下不會出現問題,這是由於js爲咱們智能地作了隱式類型轉換。可是在少數狀況下,類型轉換也會致使一些奇奇怪怪的問題,參考下面這段代碼。javascript
//true
!!'1, 2';
//false
'1,2' == true;
//false
[1, 2] == true;
//true
'1,2' == [1, 2];
複製代碼
若是不熟悉 javascript
的類型轉換規則,對於上述的結果可能會感到疑惑,爲何 "1,2"
強轉成 boolean
值時是 true
,與 true
進行比較時倒是 false
,一個字符串和一個數組進行比較,結果倒是 true
。在瞭解 javascript
的了類型轉換規則後,這些問題自當迎刃而解。java
在講 javascript
類型轉換規則以前,先了解下 javascript
的數據類型, javascript
類型能夠分爲兩大類,一個是原始類型,包含 number
, string
, boolean
, null
和 undefined
,還有一類是引用類型,全部的引用類型都是 object
類型的子類,包括 array
, function
以及 javascript
的一些內置對象,如 Date
, Math
等。正則表達式
轉換到 boolean 下面列出的會被轉換成false,其餘全部值都會轉換成true,後面的例子用到了 !!
將對象隱式轉換成 boolean
。數組
//false
!!undefined;
//false
!!null;
//false
!!0;
//false
!!-0;
//false
!!NaN;
//false
!!"";
//true
!!Infinity;
複製代碼
轉換到 number true
轉換成1,false
轉換成0,undefined
轉換成 NaN,null
轉換成 0,空字符串轉換成 0, 用數字表示的字符串轉換成對應數字,有空格的狀況下會自動去除空格,不能轉換成直接轉換成數字的則轉換成 NaN
,下面的例子使用算術運算符將其餘類型隱式轉換成 number
。函數
//1
+true;
//0
+false;
//NaN,這裏要和null區分開,null會轉換成0
+undefined;
//0
+null;
//0
+"";
//12
+"12";
//12,有空格忽略空格
+" 12";
//NaN,含有非數字字符,轉換成NaN;
+" 12a";
複製代碼
轉換到 string 轉換到字符串的狀況比較簡單,直接將值加上個引號就是對應的轉換值,下面的例子使用 String
構造函數進行轉換。ui
//'undefined'
String(undefined);
//'null'
String(null);
//'true'
String(true);
//'false'
String(false);
//'0'
String(-0);
//'0'
String(+0);
//'1'
String(1);
複製代碼
全部原始類型轉換成對象單純的就是調用其對應的構造方法,undefined
和 null
例外,當試圖將這兩個值轉換成對象時,會拋出一個類型錯誤(TypeError)。spa
轉換到 boolean
對象轉換到 boolean
比較簡單,全部對象都轉換成 true
,包括 new Boolean(false)
,下面的例子用到了 !!
將對象隱式轉換成 boolean
。code
//true
!!new Boolean(false);
//true
!!{};
//true
!!new Date();
複製代碼
轉換到 string
對象轉換到 string
首先會調用從 Object
那繼承來的 toString
方法,本身構造的對象的 toString
方法默認返回 [object Object]
,若是 toString
方法返回的是原始值,則將這個原始值轉換成字符串並返回,若是對象沒有 toString
方法(或許被咱們本身重寫了)或者返回的不是原始值,則 javascript
會再嘗試調用對象的 valueOf
方法,這個方法也是從 Object
那繼承過來的,本身構造的對象這個方法默認返回自身,若是 valueOf
方法返回的時原始值,則將這個原始值轉換成字符串並返回,若是對象沒有 valueOf
方法或是這個方法返回的不是原始對象,則拋出一個類型轉換錯誤。對象
var obj = {};
obj.toString = ()=> 4;
obj.valueOf = ()=> '3';
//'4'
String(obj);
obj = {};
obj.toString = undefined;
obj.valueOf = ()=> '3';
//'3'
String(obj);
obj = {};
obj.toString = ()=> obj;
obj.valueOf = ()=> '3';
//'3'
String(obj);
obj = {};
obj.toString = ()=> obj;
obj.valueOf = ()=> obj;
//throw a TypeError
String(obj);
複製代碼
上面的例子能夠很好的證實上面的描述,我特地對兩個方法的返回值的類型作了不同的處理,第一輪調用中,toString
方法返回數值4,直接轉換爲字符串 '4' 並返回。第二輪調用中,toString
方法不存在了,因此再次嘗試調用 valueOf
方法,返回一個字符串 '3',直接拿來使用,第三輪方法相似,只是 toString
方法返回了自身,第四輪調用中,toString
方法和 valueOf
都返回了自身,並非一個原始類型,因此會拋出一個類型轉換錯誤。繼承
轉換到 number
對象轉換到 number
和轉換到 string
很相似,只不過期調用方法的順序換了一下,首先會調用 valueOf
方法,若是 valueOf
方法返回的是原始值,則將這個原始值轉換成數值並返回,若是對象沒有 valueOf
方法或者返回的不是原始值,則 javascript
會再嘗試調用對象的 toString
方法,若是 toString
方法返回的時原始值,則將這個原始值轉換成數值並返回,若是對象沒有 valueOf
方法或是這個方法返回的不是原始對象,則拋出一個類型轉換錯誤。
var obj = {};
obj.toString = ()=> 4;
obj.valueOf = ()=> '3';
//3
+obj;
var obj = {};
obj.toString = ()=> 4;
obj.valueOf = undefined;
//4
+obj;
var obj = {};
obj.toString = ()=> 4;
obj.valueOf = ()=> obj;
//4
+obj;
obj = {};
obj.toString = ()=> obj;
obj.valueOf = ()=> obj;
//throw a TypeError
+obj;
複製代碼
上面這個例子基於以前的改造的,只不過是調換了 toString
和 valueOf
的優先順序,看官們看看結果就行了。
Note: 一些內置對象的 toString
和 valueOf
有本身的一套實現,如數組的 toString
方法會把數組的每個元素轉換成字符串,再使用逗號鏈接,Function class
的 toString
會將本身的定義轉換成字符串。Date class
則會返回一個可被本身解析的字符串, RegExp
的 toString
會轉換成正則表達式直接量的字符串,valueOf
返回自1970年1月1日以來的毫秒數。
還有一個須要注意的地方是,在大多數狀況下,類型轉換的目標值都是很明顯的,如在一些流程控制語句中,像 if
和 while
但願獲得一個 boolean
值,則類型轉換會向 boolean
值轉換,一些算術運算符每每但願操做數是一個數值,如 *,/等,這時候類型轉換會向 number
值轉換。 然而還有一小部分狀況是類型轉換目標值不那麼明顯,如 +
,==
, <
等,就拿 +
來講,+
既能夠作加法,也能夠作字符串的鏈接,當有一個操做數是對象時,這個對象應該轉換成什麼數值類型呢?其實 javascript
在這裏有着一種特殊的處理方法,這裏 javascript
會像轉換數值一個處理這個對象,先嚐試調用 valueOf
,再嘗試調用 toString
,可是仍是有一點區別的,這裏 javascript
不知道要將這個值轉換成什麼類型,因此這裏只作到轉換到原始值,直接返回,再也不將這個原始值轉換到number
或 string
,後面即便要再轉換,則根據各個操做符的規則來定,這個後面會說到。還有一個特別的地方,就是 Date
對象,這個對象在遇到這種狀況時,會表現得特別不同,會先嚐試調用 toString
方法,再嘗試調用 valueOf
方法,咱們直接看例子。
var obj = {};
obj.valueOf = ()=> 4
obj.toString = ()=> '3';
//5,返回數值4,和1作加法運算
1 + obj;
obj = {};
obj.valueOf = ()=> obj
obj.toString = ()=> '3';
//13,這個obj轉換成'3',和數值1相加作字符串相加操做
1 + obj;
obj = {};
obj.valueOf = undefined;
obj.toString = ()=> '3';
//13,這個obj轉換成'3',和數值1相加作字符串相加操做
1 + obj;
obj = new Date()
//一串很大的數字
obj.valueOf();
//UTC時間字符串
obj.toString();
//1和UTC時間字符串作字符串鏈接,這也能夠說明Date對象特立獨行的表現(先調用toString方法)
1 + obj;
複製代碼
瞭解了 javascript
類型轉換規則,再來聊聊啥時候 javascript
會作類型轉換操做。
顯示轉換比較簡單,若是須要轉換成原始對象,則調用不帶 new
的內置對象構造方法。
//'12'
String(12);
//'true'
String(true);
//false
Boolean("");
//true
Boolean(1);
//123
Number("123");
//NaN
Number(undefined);
var obj = {};
obj.toString = ()=> '4';
//'4'
String(obj);
複製代碼
能夠看到上面的例子徹底符合以前的轉換規則,若是要轉換成對象的話,只須要調用加了 new
關鍵字的構造方法就能夠了,結果是其對應的包裝類。
//'12'對應的字符串包裝類
new String(12);
//'true'對應的字符串包裝類
new String(true);
//false對應的Boolean包裝類
new Boolean("");
//true對應的Boolean包裝類
new Boolean(1);
//123對應的數值包裝類
new Number("123");
//NaN對應的數值包裝類
new Number(undefined);
//{}
new Object(undefined);
//{}
new Object(null);
//{}
Object(undefined);
//3對應的數值包裝類
new Object(3);
var obj = {};
obj.toString = ()=> '4';
//'4'對應的數值包裝類
new String(obj);
複製代碼
這裏有個特例要說明下,由於 Object
沒有對應的原始類型,因此加不加 new
關鍵字都是同樣的,並且前面說到 undefined
和 null
在嘗試轉換成對象時會拋出一個 TypeError
錯誤對象,上面例子的結果顯然不符合預期,其實拋出錯誤是在一些隱式轉換時的表現,若是是調用 Object
構造函數,則只是簡單地返回一個空對象而已。有人又要問了,我怎麼知道不加 new
返回的是原始類型,加了 new
返回的是對應的包裝類型呢?其實咱們調用一下 typeof
就能看出來,原始類型返回其對應的類型字符串,如'string',對象則統一返回 Object
,咱們來寫個例子。
//number
typeof Number('12');
//Object
typeof new Number('12');
複製代碼
相比於顯示轉換,隱式轉換要複雜的多,也是常常形成bug的元兇,不瞭解其機制的人,對於此類bug也難以理解。
原始類型向對象轉換。 原始類型是不含屬性和方法的,可是在 javascript
中咱們常常會看到這樣的代碼
//4
'Hello World!'.indexOf('o');
複製代碼
這裏其實作了從原始類型到封裝類型的轉換,javascript
判斷你在某個原始類型上調用方法時,會先將這個原始類型轉換成對應的包裝類型,再調用其方法,將返回值返回,隨即銷燬這個對象。這也能夠理解爲啥原始類型的屬性和方法都是隻讀的,而且不能添加新的屬性。
var a = "123";
a.len = 3;
//undefined
a.len;
複製代碼
因爲 undefined
和 null
沒有對應的包裝類型,在嘗試調用其中的方法時,會拋出一個 TypeError
錯誤對象,與以前說的吻合。
var a = undefined;
//throw a TypeError
a.toString();
a = null;
//throw a TypeError
a.toString();
複製代碼
算術運算符轉換成數值類型。 除了 +
能夠鏈接字符串之外,全部的算術運算符都指望獲得一個數值類型的操做數,如一元操做符 ++
,二元操做符 *
等,這裏只討論除了 +
之外的算術運算符,+
有本身的一套轉換規則,這個後面說。
//123
+'123'
//6
'2' * '3'
//2
'6' / '3'
複製代碼
上面的例子一元操做符 +
將一個字符串轉換成數值很是簡便,筆者在日常的開發中就是這麼用的。
邏輯運算符,流程控制語句轉換成布爾值。 邏輯運算符 !
, ||
, &&
以及一些流程控制語句會將值轉換成布爾值。 !
用於取反,調用兩次能夠很便捷的將一個值轉換成其對應的布爾值。 ||
和 &&
會有意思一點,這兩個操做符只判斷值是真值仍是假值,不作類型轉換,直接返回操做數的運算結果,真值指的是 true
以及全部能夠轉換成布爾值 true
的值,假值指的是 false
以及全部轉換到布爾值是 false
的值,這些值在前面列舉過。舉個例子,javascript
遇到 ||
的時候,會先判斷第一個操做數是真值仍是假值,若是是真值,直接返回這個操做數,而不是返回 true
,同時也不會去理會第二個操做數,這個特性被稱做短路操做,即第二個操做數根本就不會被執行,若是第一個操做數是假值,則直接返回第二個操做數的結果,而無論第二個操做數是不是真值仍是假值,這個操做符常常被用來作默認值處理。&&
操做符相似,當第一個操做數爲假值時,會作短路處理,返回第一個操做數。 一些流程控制語句如 if
, while
等會嘗試將分支判斷中的內容轉換成 boolean 值進行判斷,轉換規則和前面的一直,就很少說了。
//false,將一個數值轉換成布爾值,規則和以前說的同樣
!!0
//true,將一個對象轉換成布爾值
!!{}
//{},用||給一個變量設置默認值,第一個操做數是變量,第二個操做數是默認值,若是變量是假值的話,則將這個默認值賦值給這個變量
a = a || {};
//&&判斷一個值是否存在,經常使用來作非空校驗,若是a是假值,則不會讀取a的exist變量,這樣的程序健壯性更好
if (a && a.exist) {
doSomething();
}
//{}是真值,因此這個if分支永遠會被執行
if ({}) {
doSomething();
}
複製代碼
關係運算符。 一些關係運算符的狀況看起來會複雜一些,如 ==
, >
等,兩邊若是數據類型不一致,則會作相應的類型轉換,這邊要注意的是這裏討論的是不嚴格等於 ==
,由於嚴格等於不作類型轉換,嚴格等於當發現類型不一致,直接返回 false
,而不嚴格等於則會作適當的類型轉換,不嚴格等於的比較規則以下。
null
, 一個爲 undefined
,返回true。true
也轉換成 1。Date
對象是調用 toString
方法外,其他對象都是先嚐試調用 valueOf
再嘗試調用 toString
,而且一旦獲得原始值後再也不作類型轉換,直接返回。'>'
,'<'
的狀況很相似,這兩個操做符既能夠比較數值的大小,也能夠比較字符串的字母表順序,他們按照以下規則進行比較。
Date
對象是調用 toString
方法外,其他對象都是先嚐試調用 valueOf
再嘗試調用 toString
,而且一旦獲得原始值後再也不作類型轉換,直接返回//true
undefined == null;
//true, true轉換成1,故相等
true == 1;
//false,true轉換成1,故不相等
true == 2;
var obj = {};
obj.valueOf = ()=> '4';
//true,先將obj轉換成'4',再將字符串轉換成4,這裏轉換調用的valueOf方法,這裏toString返回的是默認值[object, Object]
obj == 4;
obj = new Date();
obj.valueOf = ()=> '4';
obj.toString = ()=> 3;
//true,日期對象先嚐試調用toString方法獲取原始數值3,而後進行比較,而後將右邊的操做數 '3' 轉換成數值3,而後進行比較
obj == '3'
複製代碼
+
運算符。
+
號運算符和嚴格等於 ==
相似,不過它更喜歡字符串,它按照以下規則進行操做。
==
。//'11',存在字符串,將兩個值轉換成字符串作鏈接操做
1 + '1';
//2,不存在字符串,則將兩邊的數都轉換成數值進行加法操做
true + true;
var obj = {};
obj.valueOf = ()=> 4;
//5,先將對象轉換成4,發現兩邊都是數值,作加法操做
1 + obj;
obj = {};
obj.valueOf = ()=> obj;
obj.toString = ()=> '4';
//'14',先嚐試調用valueOf方法,發現返回的不是原始值,再嘗試調用toString方法,返回字符串'4',而後兩邊存在一個字符串,則作字符串鏈接操做
1 + obj;
複製代碼
//true,這是一個真值,轉換成布爾值後爲true
!!'1,2';
//false,兩邊不全是字符串,轉換成數值,true轉換成1,'1,2'轉換成NaN,故不相等,結果爲false
'1,2' == true;
/** * false * 數組[1, 2]是對象,先嚐試調用valueOf()方法,返回自身仍是一個對象, * 再嘗試調用toString()方法,返回'1,2'是一個字符串,是原始值 * 將原始值'1,2'與true進行比較,結果同上 */
[1, 2] == true;
/* * true * 數組[1, 2]是對象,先嚐試調用valueOf()方法,返回自身仍是一個對象, * 再嘗試調用toString()方法,返回'1,2'是一個字符串,是原始值, * 將原始值'1,2'與'1,2'進行比較,二者類型相同,進行嚴格比較,返回true */
'1,2' == [1, 2];
複製代碼