JavaScript過載函式的實現【重構優化版】
您所瀏覽的網站已嚴重侵犯了原文作者nivk的權益。
本文由nivk撰寫,摘抄自nivk的部落格:http://blog.csdn.net/teajs
前言:為什麼我們需要JavaScript過載函式?
一把剪刀可以用來做什麼?
剪刀可以用來剪紙,也可以用來剪魚。
現在,我們把剪刀剪東西這個動作封裝成一個函式。
1、假設剪“紙”是一個String型別。
2、假設剪“肉”是一個Boolean型別。
現在,我們來實現一下。
沒錯,現在我們呼叫這個“cut”函式是可以滿足我們的需要的。function cut(obj) { if (typeof obj === "string") { console.log("我們在剪紙"); } else if (typeof obj === "boolean") { console.log("我們在剪肉"); } }
甚至有人說,我可以做優化!比如這樣:
function cut(obj) {
switch (obj) {
case "string":
console.log("我們在剪紙");
break;
case "boolean":
console.log("我們在剪肉");
break;
}
}
但是,這種程度的“優化”和本文是沒有關係的。
我們需要考慮的是更復雜的情況,如果我需要一剪刀下去,同時剪了“紙”和“肉”,這時才能達到某個神祕成就。我們該如何做?
OK,我們先列舉一下都有哪幾種情況:
1、僅僅剪了“紙”。
2、僅僅剪了“肉”。
3、同時剪了“紙”和“肉”。
4、。。。。
等等!為什麼有第四種情況?
嗯……你應該考慮一下,同時剪的時候,是先剪了“紙”,還是先剪了“肉”。
所以,我們重新列舉一遍:
1、僅僅剪了“紙”。
2、僅僅剪了“肉”。
3、同時剪了“紙”和“肉”,並且先剪了“紙”。
4、同時剪了“紙”和“肉”,並且先剪了“肉”。
好的,我們用程式碼來實現一遍:
有同學有優化方案,但這同樣不是我們要關注的地方。function cut(obj, obj1) { if (typeof obj === "string" && typeof obj1 === "boolean"){ console.log("同時剪了紙和肉,並且先剪了紙"); } else if (typeof obj === "boolean" && typeof obj1 === "string"){ console.log("同時剪了紙和肉,並且先剪了肉"); } else if (typeof obj === "string") { console.log("我們在剪紙"); } else if (typeof obj === "boolean") { console.log("我們在剪肉"); } }
重點是,這麼多判斷,你寫著不累,我看著都累啊。
而且這僅僅是4種情況,真實專案環境情況可能比複雜的多。
構思:換一種可讀性更好的方法來應對函式的“過載”。
如何使因引數變化而變化的函式內部實現更優雅?
要想實現方式優雅且更有實用性,有幾個問題亟需解決:
1、去掉函式內部的型別判斷。
2、加強函式引數的型別判斷。
3、允許某個引數的泛型別。
4、允許限制某個引數的列舉型別。
關於以上4點的具體解釋如下:
1、去掉函式內部的型別判斷。
此條無需過多解釋,所有優化點都圍繞著這一點展開。
2、加強函式引數的型別判斷。
可能有人覺得和第1條有所衝突,其實並不是。
第1條是將判斷寫在了函式內,而此條則是將其移到函式外去做。
3、允許某個引數的泛型別。
何為泛型別?因為JavaScript是弱型別語言,所以我們一直使用的都是泛型別。
但是既然我們要增加型別判斷,也不能一棒子打死。
所以我們還是需要支援泛型別的。
4、允許限制某個引數的列舉型別。
這裡我用“列舉型別”不是很合適,其實我想表達的意思是,某個引數可以是一個或多個限定範圍的型別。
現在我提出一種用法:
var fn = Overload.create().
add("Number", function (num) {
console.log("數字:" + num);
}).
add("String", function (str) {
console.log("字串:" + str);
}).
add("Number, String", function (num, str) {
console.log("數字:" + num + "字串:" + str);
}).
add("String, Number", function (str, num) {
console.log("字串:" + str + "數字:" + num);
}).
add("String || Number, Boolean", function (val, bool) {
console.log("字串或數字:" + val + "布林值:" + bool);
});
一眼望去是不是乾淨整潔了許多?而且在支援程式碼塊摺疊的編輯器中會如此顯示:
非常容易找到你想了解的函式實現區域。
廢話不多說,直接上程式碼!
!function () {
/* 私有變數 */
var any = "[object Unkonw]";
/* 私有方法 */
function getType(str) {
/// <summary>根據字串獲取Js可用於判斷的型別</summary>
/// <param name="str" type="String"></param>
/// <returns type="any" />
if (!str || !(str = str.toString().trim())) {
// 容錯,傳入了空字串
return null;
}
switch (str) {
case "Number": case "number":
case "String": case "string":
case "Boolean": case "boolean":
case "Function": case "function":
case "Object": case "object":
return str.toLowerCase();
case "Any": case "any": case "*":
return any;
case "Null": case "null": case "undefined":
throw new Error("Invalid type");
default:
return eval(str);
}
}
function processParameters(_parameters) {
/// <summary>
/// 引數型別處理
/// </summary>
var parameters;
var parameter;
for (var i = _parameters.length; i--;) {
// 遍歷所有過載引數列表
parameters = _parameters[i];
if (parameters === null) {
// 跳過不需要引數的過載函式
continue;
}
for (var x = parameters.length; x--;) {
// 遍歷某個過載的引數列表
parameter = parameters[x];
if (parameter instanceof Array) {
// 判斷引數是否存在或者判斷
for (var n = parameter.length; n--;) {
// 獲取或者判斷中每個引數的型別
parameter[n] = getType(parameter[n]);
}
} else {
// 不存在或者條件,直接獲取引數型別
parameters[x] = getType(parameter);
}
}
}
}
/* 公開靜態方法 */
window.Overload = {
create: function () {
/// <summary>建立過載物件</summary>
/// <returns type="Function" />
var _parameters = [];
var _functions = [];
var isProcessed = false;
function Overload() {
/// <summary>呼叫過載函式</summary>
if (_functions.length === 0) {
// 檢查是否有可呼叫函式
throw new Error("Function not implemented");
}
if (!isProcessed) {
// 檢查所有引數是否經過了型別處理
processParameters(_parameters);
isProcessed = true;
delete Overload.add;
}
var parameters;
for (var i = 0, len = _functions.length; i < len; i++) {
parameters = _parameters[i];
if (!parameters && !!arguments.length ||
!!parameters && arguments.length !== parameters.length) {
// 跳過引數數量不一致的過載(不包括引數列表為空的情況)
continue;
}
var checkDone = true;
if (parameters !== null) {
for (var x = 0, xLen = parameters.length; x < xLen; x++) {
// 遍歷引數型別
var checkType;
var checkTypeof;
var argTypeof = typeof arguments[x];
if (parameters[x]._isOrParameters) {
// 檢查並跳過或者判斷引數型別不一致的過載
for (var n = 0, nLen = parameters[x].length; n < nLen; n++) {
checkType = parameters[x][n];
checkTypeof = typeof checkType;
if ((checkTypeof === "string" && argTypeof !== checkType ||
checkTypeof !== "string" && !(arguments[x] instanceof checkType)) &&
checkType !== any) {
if (n + 1 == nLen) {
// 找不到任何匹配的引數
checkDone = false;
break;
}
} else {
// 找到了匹配引數,直接進入下一步
break;
}
}
} else {
// 檢查並跳過引數型別不一致的過載
checkType = parameters[x];
checkTypeof = typeof checkType;
if ((checkTypeof === "string" && argTypeof !== checkType ||
checkTypeof !== "string" && !(arguments[x] instanceof checkType)) &&
checkType !== any) {
checkDone = false;
break;
}
}
}
}
if (checkDone) {
return _functions[i].apply(this, arguments);
}
}
throw new Error("Invalid parameter");
};
Overload.add = function (str, fun) {
/// <summary>新增過載函式</summary>
/// <param name="str" type="String">引數列表字串</param>
/// <param name="fun" type="Function">過載呼叫的函式</param>
/// <returns type="OverloadFunction" />
var parameters = null;
if (typeof str === "string") {
parameters = str.trim().split(",");
for (var i = parameters.length; i--;) {
if (parameters[i].indexOf("||") >= 1) {
parameters[i] = parameters[i].split("||");
parameters[i]._isOrParameters = true;
}
}
}
_parameters.push(parameters);
_functions.push(fun);
return Overload;
};
return Overload;
}
};
}();
若您有任何想要詳細瞭解的地方可以在本文最下方評論留言,我會在看到您的留言後第一時間給您回覆!