JavaScript重載函數的實現【重構優化版】

前言:為什麼我們需要JavaScript重載函數?

一把剪刀可以用來做什麼?

剪刀可以用來剪紙,也可以用來剪魚。

現在,我們把剪刀剪東西這個動作封裝成一個函數。

1、假設剪“紙”是一個String類型。

2、假設剪“肉”是一個Boolean類型。

現在,我們來實現一下。

function cut(obj) {
    if (typeof obj === "string") {
        console.log("我們在剪紙");
    }
    else if (typeof obj === "boolean") {
        console.log("我們在剪肉");
    }
}

沒錯,現在我們調用這個“cut”函數是可以滿足我們的需要的。

甚至有人說,我可以做優化!比如這樣:

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;  
        }  
    };  
  
}();

發佈留言