﻿# 前端工程研究：關於 JavaScript 中物件的 valueOf 方法

在 JavaScript 程式語言裡，這個 valueOf() 函式算是非常少用的一個內建函式，甚至於很多人連聽都沒聽過。然而，這個 valueOf() 函式十分重要，我在研究之後發現，理解了 valueOf() 的用途後，不但更能理解 JavaScript 如何針對物件進行**數值**、**布林**與**字串**運算，更能夠利用 valueOf() 解決 **使用者定義物件** 無法比對大小的問題。

前幾天我曾在 [Will 保哥的技術交流中心](https://www.facebook.com/will.fans) 分享過一個非常好用的 [DevDocs](http://devdocs.io/) 網站，從該網站搜尋 **valueOf** 關鍵字之後，你可以查到在 JavaScript 裡，有許多內建物件都有這個 **valueOf** 方法，幾乎大部分的原始型別包裹物件都有個 valueOf() 函式，例如 [Boolean.valueOf](http://devdocs.io/javascript/global_objects/boolean/valueof) 、[Number.valueOf](http://devdocs.io/javascript/global_objects/number/valueof) 、[String.valueOf](http://devdocs.io/javascript/global_objects/string/valueof) 等等，另外還有一個 Date 內建物件也有個 [Date.valueOf](http://devdocs.io/javascript/global_objects/date/valueof) 方法，最後還有個根物件的 [Object.valueOf](http://devdocs.io/javascript/global_objects/object/valueof) 方法可用。

![](https://stwillblogassets.blob.core.windows.net/files/images/external/stwillblogassets.blob.core.windows.net/300892fb96eae123fceb-image_2197.png)

從文件中可以看見，這個 **valueOf** 方法最主要的目的，就是用來回傳**特定物件**相對應**原始型別**的值：

[![](https://stwillblogassets.blob.core.windows.net/files/images/external/stwillblogassets.blob.core.windows.net/d13895b56ec7e2f9a59d-image_thumb_183.png)](https://stwillblogassets.blob.core.windows.net/files/images/external/stwillblogassets.blob.core.windows.net/f660cd13498b2b1c4dfc-image_2198.png)

我們知道 JavaScript 的型別系統中，主要包含兩大類：

-   原始型別 (primitive types)
-   物件型別 (object types)

在 JavaScript 裡面，所有的東西都是物件，除了內建的 5 大原始型別除外，分別是：

-   number
-   string
-   boolean
-   null
-   undefined

其中 null 與 undefined 屬於特殊用途，並沒有相對應的 **原始型別包裹物件** (primititve type wrapper type)，但其他另外三個都有，分別是：

-   Number
-   String
-   Boolean

所以，當我們比較以下兩個變數時，最後比對兩個不同型別物件的**等式** (equality)，結果竟然為 **true**

![image](https://stwillblogassets.blob.core.windows.net/files/images/external/stwillblogassets.blob.core.windows.net/56cebbf0b077d16d3f5e-image_2199.png "image")

大部分的情境下，開發人員都會回答說：「因為 JavaScript 是個動態型別，所以型別會自動轉換，雖然一個型別是 number，另一個型別是 object，但用兩個等號 ( == ) 比對兩邊物件時，JavaScript 會幫我們自動轉型，然後才進行比對」。

嗯! 這種解釋，我不能說他「錯」，這段文字解釋了一個在 JavaScript 程式語言中普遍的「現象」，並沒有解釋「為什麼」？而我這次的研究，則是釐清了這個現象背後發生的原因。

事實上，當 JavaScript 任意物件在進行 **比較運算** 時，如 == , != , < , <= , > , >= 等等，都會先執行 valueOf() 或 toString() 函式，取回該物件相對應**原始型別**的值，全看你當下兩邊比較的是甚麼原始型別，然後再進行比較。原始型別包裹物件總共有三種，除了 Number 物件之外，還有 String 與 Boolean 物件，其實都是完全一樣的運作方式，我以 number 原始型別物件、string 原始型別物件與 Number 物件進行比較與示範如下圖示：

![](https://stwillblogassets.blob.core.windows.net/files/images/external/stwillblogassets.blob.core.windows.net/4fb34e98aa55c55b1a7d-image_2223.png)

除了**原始型別包裹物件**之外，我們還有許多**使用者定義物件**存在，這些物件在預設的情況下是無法進行比較運算的，舉個例子來說，我們先宣告兩個新的物件，然後去比較兩個物件，所有結果都將是 **false**。  
如下圖示，我無論用自訂物件，或取得 jQuery 物件，其結果都是無法比較的：

![](https://stwillblogassets.blob.core.windows.net/files/images/external/stwillblogassets.blob.core.windows.net/92ec6b264298ee2672cb-image_2201.png) ![](https://stwillblogassets.blob.core.windows.net/files/images/external/stwillblogassets.blob.core.windows.net/de5903f18ba35182ae35-image_2202.png)

我舉個例子來說，即便我額外實作了 valueOf() 或 toString() 方法，你在進行物件的等式運算時，得到的結果依然是 false，如下圖示：

![image](https://stwillblogassets.blob.core.windows.net/files/images/external/stwillblogassets.blob.core.windows.net/f1d02b5c66acd50825e0-image_2224.png "image")

所以，在 JavaScript 裡，所有的**物件**都是**不相等**的，每一個都是獨立的**物件實體** (object instance)，這是一個非常重要的特性，即便你實作了 valueOf 或 toString 方法，還是無法對**使用者定義物件**進行任何相等比較運算，各位必須謹記在心！

為了實驗 JavaScript 在進行比較運算時，一定會先將物件透過 valueOf() 或 toString() 方法轉換成原始型別的值，我簡單做個小實驗，我把註解都寫在如下圖上了，請各位由上往下，直接看圖說故事：  
( 以下範例僅示範用途，實務上不會去覆寫 Object 最上層物件的方法或屬性 )

![](https://stwillblogassets.blob.core.windows.net/files/images/external/stwillblogassets.blob.core.windows.net/e50a77f55fd42cf6eee6-image_2225.png)

**請記得**：只有**原始型別**物件可以比較 **等式** (equality)，**物件型別**物件是**無法比較等式**的，即便是**原始型別包裹物件**也一樣！

![](https://stwillblogassets.blob.core.windows.net/files/images/external/stwillblogassets.blob.core.windows.net/5f16d1d97b8be1969d45-image_2205.png)

不過，如果你拿**物件型別**來比較**原始型別**，所有的物件型別物件，一定會透過 valueOf() 或 toString() 方法先轉成原始型別物件，然後才進行比較，這也是大家熟知的「自動型別轉換」。而這個**自動型別轉換**的過程，骨子裡就是透過 valueOf() 或 toString() 方法轉換的！除此之外，你又可以自訂 valueOf() 或 toString() 方法，因此，這個「自動型別轉換」的過程，完全可以讓你自由控制，這將對我們的 JavaScript 應用程式帶來極大的彈性！

我常聽到許多 Web 開發人員誤解 JavaScript 這個程式語言，認為他是弱型別，或物件的值在比較的時候隨便亂轉換，導致經常判斷失誤，寫出一些很難查出來的 Bug，事實上都是因為對 JavaScript 語言特性不了解所致。接下來，我再示範一個例子，這次我們使用建構式函式建立一個自訂物件，然後再進行各種比較運算。

![](https://stwillblogassets.blob.core.windows.net/files/images/external/stwillblogassets.blob.core.windows.net/f15c7d362c2c6acd8f5b-image_2206.png)

**註**: 程式碼本身應該就可以解釋一切，如果看不太懂，真的有必要多做進修，或報名下一梯次【前端工程訓練：JavaScript 與 AngularJS 開發實戰】課程，我會在課程中進行非常詳盡的解說，相關課程資訊都會在 [Will 保哥的技術交流中心](https://www.facebook.com/will.fans) 進行公告。

在了解**比較運算**之後，我們可以看看更多在**物件型別**與**不同原始型別**的交互運算的過程中，物件是如何進行轉換的。

§ **物件型別**與 **number 型別**的混和運算 (**算術運算**)

我們先定義一個 Person 自訂物件，並定義該物件型別的 valueOf 與 toString 方法，程式碼如下：

```
function Person(name, age) {
    this.name = name;
    this.age = age;
}

Person.prototype.valueOf = function () {
    return this.age;
};

Person.prototype.toString = function () {
    return this.name;
};

var p1 = new Person('Will', 18);
var p2 = new Person('John', 25);
```

我做了一連串的測試如下，你將會發現，當我們的自訂物件在做數值運算時，物件型別的資料一定會先透過 valueOf 方法運算出一個 number 型別的結果，然後才進行數值運算！

![](https://stwillblogassets.blob.core.windows.net/files/images/external/stwillblogassets.blob.core.windows.net/4351ff4795ee1bed43a1-image_2226.png)

當然的，由於 valueOf 是你自行定義的，該方法回傳什麼結果都有可能，如果今天 valueOf 回傳的結果不是 number 型別怎麼辦？我們在來看看這個不一樣的例子。  
**注意**: 雖然 valueOf 可以回傳不是 number 型別的值，但實務上不建議這麼做！本範例僅示範用途。

我們讓 Person 物件透過 valueOf 回傳時，回傳的是 boolean 原始型別，這時只會有 true 與 false 的值被回傳，並不是 number 型別，這時 JavaScript 會強迫把 boolean 轉換成 number 型別之後，才會進行數值運算，因為我們用的是「算術運算子」，兩邊的物件，就會先轉成「數值」才會進行運算。

![](https://stwillblogassets.blob.core.windows.net/files/images/external/stwillblogassets.blob.core.windows.net/248aeed0d266405b692a-image_2208.png)

對物件進行**算數運算**的小結是：  
**使用者定義物件進行算數運算時，會以 valueOf() 方法的回傳結果為主，如果沒有自定義 valueOf() 方法，就會改以 toString() 方法的回傳結果為主。**

§ **物件型別**與 **boolean 型別**的混和運算 (**布林運算**)

在對物件進行布林運算時，各位應該會很直覺得想到 valueOf 的運算過程，要是真的這麼簡單，就沒有這篇文章啦！事實上，在對**物件型別**進行**布林運算**時，所有的**物件型別物件**只會回傳 **true** 而已！  
**注意**: 當變數值變成 null 或 undefined 時，該變數指向的物件，已經不是物件，而是原始型別。

所以我們來看看以下例子，各位就可以發現，所有物件進行布林運算，一定會轉換成 true，即便是空物件也一樣，如下範例：

![](https://stwillblogassets.blob.core.windows.net/files/images/external/stwillblogassets.blob.core.windows.net/1a7e491675b4f6644098-image_2227.png)

對物件進行**布林運算**的小結是：  
**使用者定義物件進行布林運算時，永遠會回傳 true，不會參考 valueOf 或 toString 的執行結果。**

§ **物件型別**與 **string 型別**的混和運算

物件型別在進行字串運算時，其過程相對簡單，預設所有物件都會執行 toString() 方法產生字串，當使用者定義物件沒有宣告 toString() 方法時，就會進一步執行 valueOf() 方法，如果 valueOf() 方法的執行結果不是 string 型別，則會再將其執行結果執行 toString() 產生字串，如果 toString() 與 valueOf() 方法都不存在，就會直接回傳 Object.prototype.toString 方法的執行結果，也就是回傳物件的型別名稱。

舉個例子來說，我們試著把 valueOf 回傳一個 boolean 型別：

![](https://stwillblogassets.blob.core.windows.net/files/images/external/stwillblogassets.blob.core.windows.net/a9c76a99d0e0c296fc2f-image_2210.png)

![](https://stwillblogassets.blob.core.windows.net/files/images/external/stwillblogassets.blob.core.windows.net/cdcc7ce8a2096fb6cf64-image_2211.png)

![](https://stwillblogassets.blob.core.windows.net/files/images/external/stwillblogassets.blob.core.windows.net/2e77f0257ecca726458f-image_2212.png)

對物件進行**字串運算**的小結是：  
**使用者定義物件進行字串運算時，會以 toString() 方法的回傳結果為主，**如果沒有自定義 toString() 方法，就會改以 valueOf() 方法的回傳結果為主，而 valueOf() 方法回傳的結果不是 string 型別的話，還會把 valueOf() 方法回傳的結果再執行一次 toString() 轉型成 string 型別******。**

§ **物件型別**與 **Array 型別**的關係

我們都知道 [Array 物件有個 sort 方法](http://devdocs.io/javascript/global_objects/array/sort)，可以用來排序一個陣列中的元素排列。我們先來看一個簡單的範例。如下圖示，由於陣列元素都是數值(number)，透過 sort() 排序之後，預設會採順向排序。

![](https://stwillblogassets.blob.core.windows.net/files/images/external/stwillblogassets.blob.core.windows.net/9059a779709b094372f3-image_2213.png)

事實上，**陣列**的 [sort()](http://devdocs.io/javascript/global_objects/array/sort) 方法其實是透過**字串**進行排序的，所以上述執行結果只是個**假象**，例如我們輸出 \[1000, 200, 30, 4\].sort() 的運算結果，竟不是預期的 \[4, 30, 200, 1000\]，而是 \[1000, 200, 30, 4\]，如下圖示：

[![image](https://stwillblogassets.blob.core.windows.net/files/images/external/stwillblogassets.blob.core.windows.net/dcac69fe9b8e36af8590-image_thumb_184.png "image")](https://stwillblogassets.blob.core.windows.net/files/images/external/stwillblogassets.blob.core.windows.net/34424d78801edddbf96b-image_2221.png)

所以，我們如果要修改 Person 型別的預設排序，添加一個 toString 方法，並回傳一個字串值，就能直接使用 [sort()](http://devdocs.io/javascript/global_objects/array/sort) 進行排序了！

[![image](https://stwillblogassets.blob.core.windows.net/files/images/external/stwillblogassets.blob.core.windows.net/a2226dd880f053f00ce7-image_thumb_185.png "image")](https://stwillblogassets.blob.core.windows.net/files/images/external/stwillblogassets.blob.core.windows.net/f69e0cd081e18e7135a8-image_2222.png)

我們在來看看另一個 [sort()](http://devdocs.io/javascript/global_objects/array/sort) 的用法，也就是傳入一個回呼函式 (callback) 來自訂排序規則：

```
var numbers = [3, 7, 2, 9, 1, 4, 8];
numbers.sort(function(a, b) { return a - b; });
numbers.sort(function(a, b) { return b - a; });
```

  ![](https://stwillblogassets.blob.core.windows.net/files/images/external/stwillblogassets.blob.core.windows.net/ff6671be70db0459b348-image_2214.png)

如果我們的陣列元素都是 **Person 物件型別**，而且希望以 **age 屬性**透過**數值**排序的話，我們可以透過回呼函式自行定義排序的規則，並且指定 age 屬性進行算數運算，就能達成任務，如下圖示：

![](https://stwillblogassets.blob.core.windows.net/files/images/external/stwillblogassets.blob.core.windows.net/cf59a9f6513a1f44cb7f-image_2216.png)

如果我們希望還是以上述 Person 型別的 age 屬性進行數值排序，又不希望在排序的回呼函式中明確指定屬性的話，這時我們還是可以透過自訂 valueOf 方法的方式，讓排序的時候，物件與物件的比較透過數值進行排序，如下範例：

![](https://stwillblogassets.blob.core.windows.net/files/images/external/stwillblogassets.blob.core.windows.net/5bffa57be6b4fc54e1a4-image_2218.png)

透過 valueOf 的定義，讓你的**物件型別物件**有個相對應的**原始型別物件**，事實上有助於架構你的程式，不但能賦予物件之間能夠進行原始型別的比對與運算，透過不同的使用情境，也能有更多的彈性加以變化，像是陣列的 sort() 方法就是一例，你可以在自訂排序時，也可以寫出較為通用的回呼函式，增加程式可讀性與彈性。

**結論**

最近前端工程正夯，我必須說，前端工程的複雜度，絕對沒有想像的複雜，而是比想像的還更複雜！

有很多魔鬼般的細節，會不斷卡住你的開發過程，唯有不斷的進修、磨練，才能在前端的領域悠遊，可以想見的，這是一條漫長且有趣的道路，有解不完的有趣問題，也有看不完的教學文件，等累積了夠多的知識，自然能發現別人看不見的細節，發揮別人想不到的創意，在前端這個領域享受創作的過程！

**相關連結**

-   [JavaScript and valueOf | JavaScript, JavaScript...](http://javascriptweblog.wordpress.com/2010/05/03/the-value-of-valueof/)
-   [The Secret Life of JavaScript Primitives | JavaScript, JavaScript...](http://javascriptweblog.wordpress.com/2010/09/27/the-secret-life-of-javascript-primitives/)
-   [The Value of valueOf](https://web.archive.org/web/20160304013222/http://sidekicksrc.com/post/the-value-of-valueof/)
-   [Value Object](http://c2.com/cgi/wiki?ValueObject)
-   [Sorting a JavaScript array using array.sort()](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/sort)
-   [DevDocs — API Documentation Reader](http://devdocs.io/)
-   [JavaScript Operators](http://www.w3schools.com/js/js_operators.asp)
