通過JavaScript 柯里化求加法運算引發的對JavaScript類型轉換的思考

建議先看看下面的這兩篇文章,便於本文的理解。
http://blog.csdn.net/u010533180/article/details/54598283
http://blog.csdn.net/u010533180/article/details/54427200

各大JavaScript 面試題基本都有柯里化方面的面試題,比如網易就有如下的面試題:
實現一個函數,運算結果可以滿足如下預期結果:

add(1)(2) // 3  
add(1, 2, 3)(10) // 16  
add(1)(2)(3)(4)(5) // 15

看到這樣的面試題,首先就想到了使用加法運算,如下:

function add(m) {
                var res = m;
                function plus(n) {
                    res += n;
                    return plus;
                }
                return plus;
            };

經過驗證發現輸出的結果都是plus裏面的定義。如下:

function plus(n) {
                    res += n;
                    return plus;
                }

後來經過查詢各方面資料,可以重寫toString()和valueOf()方法得到最終的結果。

function add(m) {
                var res = m;
                function plus(n) {
                    res += n;
                    return plus;
                }
                plus.valueOf= function () {
                    return res;
                };
                return plus;
            };

valueOf 和 toString
先來簡單瞭解下這兩個方法:

Object.prototype.valueOf()

用 MDN 的話來說,valueOf() 方法返回指定對象的原始值。

JavaScript 調用 valueOf() 方法用來把對象轉換成原始類型的值(數值、字符串和布爾值)。但是我們很少需要自己調用此函數,valueOf 方法一般都會被 JavaScript 自動調用。

記住上面這句話,下面我們會細說所謂的自動調用是什麼意思。

Object.prototype.toString()

toString() 方法返回一個表示該對象的字符串。

每個對象都有一個 toString() 方法,當對象被表示爲文本值時或者當以期望字符串的方式引用對象時,該方法被自動調用。

先了解下 javascript 的幾種原始類型,除去 Object 和 Symbol,有如下幾種原始類型:

Number
String
Boolean
Undefined
Null

在 JavaScript 進行對比或者各種運算的時候會把對象轉換成這些類型,下面逐一說明:

String 類型轉換
在某個操作或者運算需要字符串而該對象又不是字符串的時候,會觸發該對象的 String 轉換,會將非字符串的類型嘗試自動轉爲 String 類型。系統內部會自動調用 toString 函數。舉個例子:

var obj = {name: 'huluwa'};
var str = '123' + obj;
console.log(str);  // 123[object Object]

轉換規則:

如果 toString 方法存在並且返回原始類型,返回 toString 的結果。
如果 toString 方法不存在或者返回的不是原始類型,調用 valueOf 方法,如果 valueOf 方法存在,並且返回原始類型數據,返回 valueOf 的結果。
其他情況,拋出錯誤。
上面的例子實際上是:

var obj = {name: 'huluwa'};
var str = '123' + obj.toString();
其中,obj.toString() 的值爲 "[object Object]"

假設是數組:

var arr = [1, 2];
var str = '123' + arr;

console.log(str); // 1231,2

上面 + arr ,由於這裏是個字符串加操作,後面的 arr 需要轉化爲一個字符串類型,所以其實是調用了 + arr.toString() 。

但是,我們可以自己改寫對象的 toString,valueOf 方法:

var objOverWrite = {
    toString: function() {
        console.log('調用了 objOverWrite .toString');
        return {};
    },
    valueOf: function() {
        console.log('調用了objOverWrite .valueOf')
        return '0';
    }
}

alert(objOverWrite);//注意這裏是alert
// 調用了 objOverWrite.toString
// 調用了 objOverWrite.valueOf
// 0

var objOverWrite = {
    toString: function() {
        console.log('調用了 objOverWrite .toString');
        return {};
    },
    valueOf: function() {
        console.log('調用了objOverWrite .valueOf')
        return '0';
    }
}

console.log(objOverWrite+1);//這裏使用了加法運算,如果直接輸出objOverWrite 咋把定義直接輸出
調用了objOverWrite .valueOf
 01

上面 alert(objOverWrite) ,objOverWrite會自動調用自己的 obj.toString() 方法轉化爲原始類型,如果我們不重寫它的 toString 方法,將輸出 [object Object] ,這裏我們重寫了 toString ,而且返回了一個原始類型字符串 0 ,所以最終 alert 出了 0。

上面的轉化規則寫了,toString 方法需要存在並且返回原始類型,那麼如果返回的不是一個原始類型,則會去繼續尋找對象的 valueOf 方法,如果valueOf返回也不是原始類型,則直接拋出錯誤。

Number 類型轉換
上面描述的是 String 類型的轉換,很多時候也會發生 Number 類型的轉換:

調用 Number() 函數,強制進行 Number 類型轉換
調用 Math.sqrt() 這類參數需要 Number 類型的方法
obj == 1 ,進行對比的時候
obj + 1 , 進行運算的時候
與 String 類型轉換相似,但是 Number 類型剛好反過來,先查詢自身的 valueOf 方法,再查詢自己 toString 方法:

如果 valueOf 存在,且返回原始類型數據,返回 valueOf 的結果。
如果 toString 存在,且返回原始類型數據,返回 toString 的結果。
其他情況,拋出錯誤。
按照上述步驟,分別嘗試一下:

var obj = {
    valueOf: function() {
        console.log('調用 valueOf');
        return 0;
    }
}

console.log(obj + 1);
// 調用 valueOf
// 6
var obj = {
    valueOf: function() {
        console.log('調用 valueOf');
        return {};
    },
    toString: function() {
        console.log('調用 toString');
        return 0;
    }
}

console.log(obj + 1);
// 調用 valueOf
// 調用 toString
// 1
var obj = {
    valueOf: function() {
        console.log('調用 valueOf');
        return {};
    },
    toString: function() {
        console.log('調用 toString');
        return {};
    }
}

console.log(obj + 1);
// 調用 valueOf
// 調用 toString
// Uncaught TypeError: Cannot convert object to primitive value

Boolean 轉換

布爾比較時
if(obj) , while(obj) 等判斷時
簡單來說,除了下述 6 個值轉換結果爲 false,其他全部爲 true:

undefined
null
-0
0或+0
NaN
''(空字符串)
Boolean(undefined) // false
Boolean(null) // false
Boolean(0) // false
Boolean(NaN) // false
Boolean('') // false
 

Function 轉換

我們定義一個函數如下:

function fun() {
    var a = 1;
    console.log(1);
}

如果我們僅僅是調用 fun而不是 fun() ,看看會發生什麼?

function fun() {
    var n = 1;
    console.log(1);
} 
fun
輸出結果爲:
function fun() {
    var n = 1;
    console.log(1);
}

試試直接輸出fun.valueOf()

function fun() {
    var n = 1;
    console.log(1);
}
fun.valueOf();

輸出結果爲:
function fun() {
    var n = 1;
    console.log(1);
}

發現直接調用fun和調用fun.valueOf()方法,輸出結果是一樣的。現在讓我們改寫一下 fun函數的 valueOf 方法。

fun.valueOf = function() {
    console.log('調用 valueOf 方法');
    return 2;
}

fun;
// 輸出如下:
// 調用 valueOf 方法
// 2

與 Number 轉換類似,如果函數的 valueOf 方法返回的不是一個原始類型,會繼續找到它的 toString 方法:

fun.valueOf = function() {
    console.log('調用 valueOf 方法');
    return {};
}

fun.toString= function() {
    console.log('調用 toString 方法');
    return 3;
}

fun;
// 輸出如下:
// 調用 valueOf 方法
// 調用 toString 方法
// 3

現在讓我們看看之前提到的柯里化加法運算:

function add(m) {
  console.log("進入add方法");
                var res = m;
                function plus(n) {
                 console.log("進入plus方法");
                    res += n;
                    return plus;
                }
               plus.valueOf= function () {
               console.log("valueOf方法");
                    return res;
                };
               plus.toString= function () {
               console.log("toString方法");
                    return res;
                };
                return plus;
            };

當調用一次 add 的時候,實際是是返回 plus 這個 function,實際是也就是返回 plus .valueOf();

add(1);
進入add方法
valueOf方法
function 1

當鏈式調用兩次的時候:

add(1)(2);
進入add方法
進入plus方法
valueOf方法
function 3

當鏈式調用三次的時候:

add(1)(2)(3);
進入add方法
進入plus方法
進入plus方法
valueOf方法
function 6

我們通過谷歌瀏覽器輸出的最後一條數據看到最終的結果前面有個function 代表是裏面的plus方法返回的,但是我們重寫了valueOf方法,則會調用這個valueOf 返回對應的原始值。如果沒有重寫valueOf或者toString()方法,則直接輸出plus的定義了。

這裏有個規律,如果只改寫 valueOf() 或是 toString() 其中一個,會優先調用被改寫了的方法,而如果兩個同時改寫,則會像 Number 類型轉換規則一樣,優先查詢 valueOf() 方法,在 valueOf() 方法返回的是非原始類型的情況下再查詢 toString() 方法。

建議讀者可以仔細閱讀下面這篇文章,講的很詳細。

http://www.cnblogs.com/coco1s/p/6509141.html

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