讓 JavaScript 輕鬆支援函數重載 (Part 2 – 實現)

來源:互聯網
上載者:User

識別文本簽名
我們先來回顧一下上一篇文章中提到的Overload用例:

複製代碼 代碼如下:var extend = Overload
.add("*, ...",
function(target) { })
.add("Boolean, *, ...",
function(deep, target) { });

我們允許使用者輸入一個字串,表示某一個重載的簽名。在使用者調用函數時,我們需要拿著使用者輸入的參數執行個體去跟簽名上的每一個參數類型作比較,因此我們需要先把這個字串轉換為類型數組。也就是說,字串"Boolean, Number, Array"應該轉換為數組[Boolean, Number, Array]。

在進行轉換之前,我們先要考慮處理兩個特殊類型,就是代表任意類型的"*",和代表任意數量的"..."。我們可以為它們定義兩個專有的類型,以便在Overload內對它們做出特殊的相容性處理:

複製代碼 代碼如下:Overload.Any = function() {};
Overload.More = function() {};

在有了這兩個類型之後,字串"Boolean, *, ..."就會被正確轉換為數組[Boolean, Overload.Any, Overload.More]。由於Overload.Any和Overload.More都是函數,自然也都可以看做類型。

在這兩個類型得到正確處理後,我們就可以開始編寫識別文本簽名的轉換函式了:

複製代碼 代碼如下:if (signature.replace(/(^\s+|\s+$)/ig, "") == "") {
signature = [];
} else {
signature = signature.split(",");
for (var i = 0; i < signature.length; i++) {
var typeExpression = signature[i].replace(/(^\s+|\s+$)/ig, "");
var type = null;
if (typeExpression == "*") {
type = Overload.Any;
} else if (typeExpression == "...") {
type = Overload.More;
} else {
type = eval("(" + typeExpression + ")");
}
signature[i] = type;
}
}

我想這段代碼相當容易理解,因此就不再解釋了。我第一次寫這段代碼時忘記寫上面的第一個if了,導致空白簽名字串""無法被正確識別為空白簽名數組[],幸好我的unit test代碼第一時間發現了這個缺陷。看來編寫unit test代碼還是十分重要的。

匹配函數簽名
在我們得到函數簽名的類型數組後,我們就可以用它和輸入參數的執行個體數組做匹配了,以此找出正確的重載。在討論具體如何匹配函數簽名以前,我們先來看看C#或VB.NET這樣的語言是如何處理函數重載匹配的。一般語言進行函數重載匹配的流程都是這樣子的:

參數個數 - 參數個數不對的重載會被排除掉
參數類型 - 參數類型無法隱式轉換為簽名類型的會被排除掉
匹配個數 - 排除完畢後,剩下匹配的簽名個數不同處理方法也不同
0個匹配 - 沒有命中的匹配
1個匹配 - 這個就是命中的匹配
2個或以上的匹配 - 如果能在這些匹配中找出一個首選,那就命中首選;否則不命中任何匹配
在這一節裡面,我們先處理流程中的前兩個步驟,把參數個數或參數類型不一致的簽名去掉:

複製代碼 代碼如下:var matchSignature = function(argumentsArray, signature) {
if (argumentsArray.length < signature.length) {
return false;
} else if (argumentsArray.length > signature.length && !signature.more) {
return false;
}
for (var i = 0; i < signature.length; i++) {
if (!(signature[i] == Overload.Any
|| argumentsArray[i] instanceof signature[i]
|| argumentsArray[i].constructor == signature[i])) {
return false;
}
}
return true;
};

為了作長度對比,我們需要在這個函數外對錶示任何參數個數的"..."作一下特殊處理:

複製代碼 代碼如下:if (signature[signature.length - 1] == Overload.More) {
signature.length = signature.length - 1;
signature.more = true;
}

這一段代碼將會整合到第一節的轉換函式末端,以便matchSignature函數能夠輕易判斷出參數與簽名是否匹配。在最理想的情況下,我們對輸入參數類型匹配到0個或1個重載,這樣我們很容易就判斷出命中哪個重載了。但如果有2個或以上的重載匹配,那麼我們就要從中挑選一個最優的了,這正是下一節要討論的內容。

處理多重匹配
關於C#是如何從多重匹配中選出較為匹配的重載,可以看C# Language Specification中的有關章節。我覺得通過三個簡單的例子就能說明問題:

複製代碼 代碼如下:long Sum(int x, int y) { return x + y; }
long Sum(long x, long y) { return x + y; }
Sum(0, 1);

由於0和1這兩個參數會被編譯器理解為int類型,對於第1個重載它們都不用進行類型轉換,都與第2個重載它們都要進行類型轉換,因此第1個重載較優。

複製代碼 代碼如下:long Sum(int x, long y) { return x + y; }
long Sum(long x, int y) { return x + y; }
Sum(0, 1);

在第1個參數上,第1個重載較優;在第2個參數上,第2個重載較優。在這種情況下,任何一個重載都不優於另一個,找不到較優重載編譯器就報錯。

複製代碼 代碼如下:long Sum(int x, int y) { return x + y; }
long Sum(int x, long y) { return x + y; }
long Sum(long x, int y) { return x + y; }
Sum(0, 1);

在第1個參數上,第1個重載優於第3個重載,於第2個重載無異;在第2個參數上,第1個重載優於第2個重載,於第3個重載無異。儘管第2個重載於第3個重載分不出個優劣來,但我們可以確定第1個重載比它們都要好,因此編譯器選擇了第1個重載。

假設我們有一個overloadComparator的比較函數,可以比較任意兩個簽名之間的優劣,我們需要對簽名僅僅兩兩比較,以找出最優重載嗎?事實上是不需要的,我們可以利用Array的sort方法,讓它調用overloadComparator進行排序,排序後再驗證前兩名的關係就可以了——如果並列,則不命中任何一個;如果有先後之分,則命中第一個。

具體的overloadComparator代碼就不在這裡給出了,它依賴於另一個名為inheritanceComparator的比較函數來對比兩個簽名的參數類型哪一個更貼實際傳入的參數類型,裡面用到了一種比較巧妙的方法來判斷兩個類型是否為繼承關係,以及是誰繼承自誰。

小結
現在我們有了一個JavaScript的函數重載庫,完整代碼請看這裡:函數重載庫Overload。希望這個庫能有效協助大家提升JavaScript代碼的可讀性,降低大型Ajax項目的維護成本。

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.