深入理解toString()和valueOf()函數

1.我們爲什麼要了解這兩種方法

    總所周知,toString()函數和valueOf函數,這兩個函數是Object類的對象生來就擁有的,而且他們還可以允許我們重寫,那麼,這兩個函數到底有什麼用呢?

 從名稱上判斷,toString()將對象轉換爲字符串,valueOf將對象轉化爲值.那麼問題來了:什麼時候需要將對象轉換爲值,又什麼時候需要將對象轉換爲字符串呢?------這是我們這篇文章的核心。

   在談這個問題之前,我們先看一道題目:       

   這是一道很經典的題目,考察的就是我們對於一些基本概念的理解,當然也是一道很難的題目,甚至第一眼看起來是一道不可能實現的題目.

1
請用javascript語言實現,  var a= add(1)(2)(3)(4)(5); //結果爲5個數相加,15------------來源於http://dmitry.baranovskiy.com/post/31797647 

 

 答案

 

..上面的這道題目不是那麼容易想到,而且一般來說,程序員也不會寫這種自己看着都彆扭的代碼..我們只是以此來引申出一些最基本的概念,也是這篇文章存在的必要性。

從上面的舉例中,我們可以看出: 在輸出的時候,伴隨着Function類型向String類型之間的轉換。那麼,這種轉換是怎麼發生的呢? 在第二部分,會給出令您滿意的解釋!

 

 現在我們先需要知道一點: toString和valueOf這兩個函數,是解釋器用來幫我們自動完成類型之間的轉換(一般是對象到基本類型的轉換),進而輸出令我們滿意的結果。

 


2. 對象向基本數據類型的轉換的規則

 (1)vauleOf優先於toString()被調用---------當對象作爲操作數的時候(Date類型的除外)

先來看看下面操作運算時,數據類型的轉換

複製代碼
 1 var x="10";
 2 
 3 var a=+x;
 4 console.log(typeof a);     
 5 
 6 
 7 var b=a+x;
 8 console.log(typeof b);    
 9 
10 var object={};
11 object[a]=10000;
12 console.log(object[10]);  
複製代碼
 輸出結果

上面只是我列舉出的三種比較常見的類型轉換,也是一些最基本的概念...下面花一點時間來解析這三種類型轉換是怎麼發生的

3-4行:  "+"號作爲一元運算符---- 在這個運算符下,對象向基本數據類型的轉換規則:

              (1).當操作數是基本數據類型的時候,調用Number()函數,將其轉換爲數值

                (2). 當操作數是對象的時候,調用對象的toString或者valueOf函數,將對象轉化爲基本數據類型的值,然後再對該值調用Number()函數。

 

 所以,根據以上的轉換規則,上述的輸出結果爲 number 也就合理了.

 但是,第二條轉換規則裏,蘊含了一個好大的坑啊:到底是調用toString()還是valueOf(),要是兩個函數都能將對象轉換爲基本數據類型呢(比如Date類型的對象),你又調用誰呢?

  類似下面這樣:

複製代碼
var x = {
  toString: function () {
    return '0';
  },
  valueOf: function () {
    return 1;
  }
}
var a = + x;  //在這一步,到底調用的是toString()還是valueOf()呢?,在 + 號作爲二元運算符的部分,會給出解釋
console.log(a);
複製代碼
 輸出結果

 

7-8行:“+”號作爲二元運算符 ----- 在這個運算符下,對象向基本數據類型的轉換規則:

   當“+”號作爲2元運算符的時候情況就比較複雜了,因爲"+"號可以當做字符串的連接,也可以當做數字的相加減。你可以查下轉換規則,怎麼也有個7,8條,看的人眼花撩換。我經過大量的測試,也查閱了一些的資料,總結出以下規律:

複製代碼
//僞代碼,
a + b運算轉換規則:
var Pa = toPrimitive(a); var pb = toPrimitive(b); if ((Pa is String) || (Pb is String)) { return contact(a, b) } if ((Pa is Number) && (Pb is Number)) { return a+b; } throw error;
//注: toPrimitive 是將操作數轉化爲基本數據類型,優先調用valueOf,若得到基本數據類型,則結束,否則繼續掉用toString()。(也就是說valueOf的優先級高於toString())
複製代碼

  熟知了這些,也就可以看出,+號作爲一元運算符轉換規則其實和其作爲二元運算符差不多,轉化爲基本數據類型的時候總是優先調用valueOf()。下面來一個例子來驗證:

複製代碼
 1 var test = {
 2   valueOf: function () {
 3     return 1;
 4   },
 5   toString: function () {
 6     return '0';
 7   }
 8 }
 9 console.log( + test);   //1
10 var result = test + test;
11 console.log(result);  //2
複製代碼

 談完了上面這些,我們可以總結得出結論: 在操作數運算的時候(無論是一元的還是二元的運算),數據類型之間的轉換總是優先調用valueOf()函數,但是“+”號在作爲二元運算符時,這種優先順序在應用於Date類型的對象時,被逆轉了(toString的優先級較高)

   謹記:Date類型是一個特例,當且僅當在+號運算,且“+”號作爲二元運算符時,toString()優先調用,比如下面這樣,

複製代碼
var date=new Date();

console.log(+date);  //仍然優先調用valueOf  
console.log(date+"toString優先被調用");
 
//輸出結果:
// 1421293488713
//Thu Jan 15 2015 11:44:48 GMT+0800toString優先被調用
複製代碼

 

(2):toString()優先於valueOf()被調用 ------當你想要輸出結果是字符串的時候  

當訪問Object類型對象的變量,我們用[] 這種方括號訪問的時候,方括號的內容總是優先轉化爲字符串,也就是優先調用 toString()函數。看下面這個例子:

複製代碼
 1 var test = {
 2   toString: function () {
 3     return '0'
 4   },
 5   valueOf: function () {
 6     return 1;
 7   }
 8 };
 9 
10 var object={};
11 object[test]=1000;
12 console.log(object);  // 輸出結果:Object { 0=1000}
複製代碼

此時調用的原則描述如下:

複製代碼
[a]以這種形式訪問的時候:
var Pa=toPrimitive(a);
if(Pa is prmitive){
    var str=String(Pa);
    }else{
throw error;//cannot convert to string
    }
[str]//str爲字符串的形式
   //注: toPrimitive()此時優先調用toString()函數,若結果爲基本類型,返回,否則繼續調用valueOf();
 
複製代碼

還有幾種toString優先於valueOf()被調用的例子

 

 對象直接輸出,優先調用toString()

 

 

 

 二,數組轉化爲字符串優先選擇toString

 

 

 


 3.爲什麼會出現這些奇怪的現象

由上面可以看出,toString和valueOf這兩個函數在轉換的時候都是可能被調用的,只是在不同的環境下,調用的優先級不一樣而已。這些對於我們程序員來說,可能是透明的,但是對於解釋器說,它們乾的活可就多了...一句話歸結:解釋器總是根據語境儘可能的轉化爲我們想要的結果。換句話說:

 

   對象在作爲操作數時,解釋器總是優先調用valueOf()--(Date類型的對象在二元“+”運算時例外),而其他情況,解釋器總是認爲我們想要的是字符串,所以會優先調用toString()..

          注:Date類型的對象之所以會在二元+運算時優先調用toString(),也是因爲我們大多數情況下, 時間總是和字符串連接使用,而時間和一個數字相加的情況好少,所以Date類型中,toString()優先級才比較高。

 

 正是因爲解釋器總想完美的輸出我們想要的結果,纔會造成這種雜亂的現象和規則出現。天下間本沒有完美的事物,矛和盾總是相依相存。

 懂得規則,才能利用規則,回過頭來看一下我們在最開始所出的題目吧。是不是就是對這些規則的合理利用呢。

 

 

 

                    題外話: 寫這篇文章就是爲了表明基礎知識的重要性,並非追求一個稀奇古怪的程序,程序員追求的應該是通俗易懂的代碼(大道至簡)而不是這些看起來四不像的程序,切記本末倒置!

                                  另外,看完了這些是不是有一種回頭重新看書的衝動呀,如果有,那我的目的就達到了...哈哈!

備註: toLocaleString()這個函數是實現字符串的本地化的輸出,一般和toString()輸出的結果相同,沒什麼特殊的,就是一個普通的函數。在Date類型中,這個函數被重寫了。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章