1. 程式人生 > >JavaScript過載函式的實現【重構優化版】

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

您所瀏覽的網站已嚴重侵犯了原文作者nivk的權益。

本文由nivk撰寫,摘抄自nivk的部落格:http://blog.csdn.net/teajs

前言:為什麼我們需要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;
        }
    };

}();

若您有任何想要詳細瞭解的地方可以在本文最下方評論留言,我會在看到您的留言後第一時間給您回覆!大笑