JavaScript 通过模式匹配实现“重载”

昨天rank同学向我提出一个问题,在实际应用中有些接口需要提供类似于函数重载的功能,以方便开发者组织代码逻辑,简化使用者调用。

正好infinte同学提出“更优雅的兼容”其实也和这个问题有一定的关联(我们后面会看到)

在youa的脚本库中Function的Helper中,添加支持重载的模式匹配

/**
 * 函数参数重载方法 overload,对函数参数进行模式匹配。默认的dispatcher支持*和...以及?,"*"表示一个任意类型的参数,"..."表示多个任意类型的参数,"?"一般用在",?..."表示0个或任意多个参数
 * @method overload
 * @static
 * @optional {dispatcher} 用来匹配参数负责派发的函数
 * @param {func_maps} 根据匹配接受调用的函数列表
 * @return {function} 已重载化的函数
 */
overload: function(dispatcher, func_maps) {
    if (! (dispatcher instanceof Function)) {
        func_maps = dispatcher;
        dispatcher = function(args) {
            var ret = [];
            return map(args, function(o) {return getType(o)}).join();
        }
    }

    return function() {
        var key = dispatcher([].slice.apply(arguments));
        for (var i in func_maps) {
            var pattern = new RegExp("^" + i.replace("*", "[^,]*").replace("...", ".*") + "$");
            if (pattern.test(key)) {
                return func_maps[i].apply(this, arguments);
            }
        }
    };
},

FunctionH.overload 包括两个参数,一个是负责处理匹配条件的dispatcher函数(可缺省),另一个是一组函数映射表,默认dispatcher函数是根据实际调用的参数类型生成一个字符串,例如调用的三个参数依次为10、”a”、[1,2]将生成”number,string,array”,具体实现模式匹配的时候,将根据函数映射表的每一个”key”生成一个正则表达式,用这个正则表达式匹配dispatcher函数的返回值,如果匹配,则调用这个key对应的处理函数,否则依次匹配下一个key,例如:

getEx: function(obj, prop, returnJson) {
    var ret, propType = ObjectH.getType(prop);
    if (propType == 'array') {
        if (returnJson) {
            ret = {};
            for (var i = 0; i & lt; prop.length; i++) {
                ret[prop[i]] = ObjectH.getEx(obj, prop[i]);
            }
        } else {
            //getEx(obj, props)
            ret = [];
            for (var i = 0; i & lt; prop.length; i++) {
                ret[i] = ObjectH.getEx(obj, prop[i]);
            }
        }
    } else {
        //getEx(obj, prop)
        var keys = (prop + "").split(".");
        ret = obj;
        for (var i = 0; i & lt; keys.length; i++) {
            ret = ret[keys[i]];
        }
        if (returnJson) {
            var json = {};
            json[prop] = ret;
            return json;
        }
    }
    return ret;
},

上面这种情况下“万恶”的 if 可以优化为:

getEx: FunctionH.overload({
    "*,array,*": function(obj, prop, returnJson) {
        if (returnJson) {
            ret = {};
            for (var i = 0; i & lt; prop.length; i++) {
                ret[prop[i]] = ObjectH.getEx(obj, prop[i]);
            }
        } else {
            ret = [];
            for (var i = 0; i & lt; prop.length; i++) {
                ret[i] = ObjectH.getEx(obj, prop[i]);
            }
        }
        return ret;
    },
    "*,string,*": function(obj, prop, returnJson) {
        var keys = (prop + "").split(".");
        ret = obj;
        for (var i = 0; i & lt; keys.length; i++) {
            ret = ret[keys[i]];
        }
        if (returnJson) {
            var json = {};
            json[prop] = ret;
            return json;
        }
        return ret;
    }
}),

OK,这种形式在一些人看来或许已经比原来看起来好一些了,但是其实还可以更进一步的——

getEx: FunctionH.overload(function(args) {
    return "prop is " + ObjectH.getType(args[1]);
},{
    "prop is array": function(obj, prop, returnJson) {
        if (returnJson) {
            ret = {};
            for (var i = 0; i & lt; prop.length; i++) {
                ret[prop[i]] = ObjectH.getEx(obj, prop[i]);
            }
        } else {
            //getEx(obj, props)
            ret = [];
            for (var i = 0; i & lt; prop.length; i++) {
                ret[i] = ObjectH.getEx(obj, prop[i]);
            }
        }
        return ret;
    },
    "prop is string": function(obj, prop, returnJson) {
        var keys = (prop + "").split(".");
        ret = obj;
        for (var i = 0; i & lt; keys.length; i++) {
            ret = ret[keys[i]];
        }
        if (returnJson) {
            var json = {};
            json[prop] = ret;
            return json;
        }
        return ret;
    }
}),

还有“讨厌”的第三个参数,干脆也一并处理了——

getEx: FunctionH.overload(function(args) {
    return "prop is " + ObjectH.getType(args[1]) + " and returnJson is " +args[2];
},{
    "prop is array and returnJson is true": function(obj, prop, returnJson) {
        ret = {};
        for (var i = 0; i & lt; prop.length; i++) {
            ret[prop[i]] = ObjectH.getEx(obj, prop[i]);
        }
        return ret;
    },
    "prop is array and returnJson is false": function(obj, prop, returnJson) {
        ret = [];
        for (var i = 0; i & lt; prop.length; i++) {
            ret[i] = ObjectH.getEx(obj, prop[i]);
        }
        return ret;
    },
    "prop is string and returnJson is true": function(obj, prop, returnJson) {
        var keys = (prop + "").split(".");
        ret = obj;
        for (var i = 0; i & lt; keys.length; i++) {
            ret = ret[keys[i]];
        }
        var json = {};
        json[prop] = ret;
        return json;
    },
    "prop is string and returnJson is false": function(obj, prop, returnJson) {
        var keys = (prop + "").split(".");
        ret = obj;
        for (var i = 0; i & lt; keys.length; i++) {
            ret = ret[keys[i]];
        }
        return ret;
    }
}),

例如说浏览器嗅探和特性探测之类种种,同理也能采用这个模式(当然这种形式有利有弊,使用者自己权衡吧)——

foo = FunctionH.overload(function() {
    return MSIE ? "IE": "NotIE";
},{
    "IE": function() {...}
    "NotIE": function() {...}
});
This entry was posted in Discussion, 中文 and tagged , , , . Bookmark the permalink. Post a comment or leave a trackback: Trackback URL.

9 Comments

  1. s
    Posted August 12, 2010 at 1:35 am | Permalink

    youa的脚本库有源码吗

  2. Posted August 12, 2010 at 4:01 am | Permalink

    很好的实现.
    JS程序员就是用了大量时间用来模拟别的语言内置的特性,是很无奈的事
    用大量时间做这些基础工作,再优化分析讨论研究怎样去做,不知道是好事还是坏事.

  3. robbenmu
    Posted August 12, 2010 at 6:03 am | Permalink

    youa的脚本库开源否?

  4. akira
    Posted August 12, 2010 at 6:32 am | Permalink

    楼上和一楼,虽然现在我还不能做明确的承诺,但是开源也是我们所希望的

  5. s
    Posted August 17, 2010 at 2:51 am | Permalink

    var ret = [];
    这个多余的吧

  6. s
    Posted August 17, 2010 at 2:54 am | Permalink

    var get = overload({
    “array”: function(a) {
    return a.length;
    },
    “!array”: function(a) {
    return get([a]);
    }
    })
    建议也实现这个

  7. s
    Posted August 17, 2010 at 3:01 am | Permalink

    还有用maps里的名来拼接正则会有隐患

  8. s
    Posted August 17, 2010 at 3:30 am | Permalink

    最后一个例子感觉举得不太好
    那些用这个模式不太适合

  9. fuyou001
    Posted October 3, 2010 at 3:58 am | Permalink

    弱弱问下楼主 youa 是百度有啊前端的js库吗

2 Trackbacks

  1. By 我不得不赞叹的methodize和curry » NoName on March 15, 2011 at 1:01 pm
  2. By I have to praise methodize and the curry on May 18, 2011 at 7:28 am

    [...] actually are and QWrap schooling.Baidu team LC classmates and Cat Chen made similar explains: http://typeof.net/2010/08/overload-via-pattern-matching-for-javascript/ http://www.cnblogs.com/cathsfz/archive/2009/07/02/1515188.htmlI believe that the Functional design [...]

Post a Comment

Your email is never published nor shared. Required fields are marked *

*
*

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>