一、背景:

在面向对象的编程语言中,通过重载机制,使得同一个方法名可以具有不同的实现,这些不同的实现版本具有不同的参数(个数、类型都可以不同)。这些不同的参数形成了方法的不同的特征(或者叫签名),从而在使用中,即使方法名相同,程序也能正确地找到对应的版本。

在JavaScript中,没有内置的重载机制,但是它对每个方法(函数)都提供了一个arguments对象,该对象具有传递过来的参数信息,我们可以利用它来实现(或者说模拟)重载机制。

为什么要写多余的代码来实现这个重载机制呢?其实一般不需要,但是在某些情况下,多加一点点代码来实现重载机制后,带来的是巨大的灵活性和可重用性。否则给做同样事情的方法或函数,仅仅因为传进来的参数稍有不同就起不同的名字,会显得非常冗余、奇怪和不容易记忆。

二、分析:

在内置重载机制的语言里,编译器要对函数的参数列表进行两种分析:

  1. 参数个数:对相同名字的方法(或者说函数),不同的参数个数对应不同的实现版本;
  2. 参数的类型:参数的个数相同,但是处于同一位置的参数类型不一样,那么也能区分出它们的不同版本。

在JavaScript中,没有内置这样的机制,我们可以在代码中用同样的方法自己实现,基本思路是也是如此:

  1. 参数个数:通过调用arguments.length,对不同个数的参数给予不同的实现;
  2. 参数的类型:通过 typeof arguments[i] 测试参数的类型,对不同的类型给予不同的代码实现。

具体实现时,需要先对所有可能的参数给予良好的默认值,当传进来的参数存在时,就用传进来的值去覆盖默认值。

三、实例:

/// 
///     序列化一个对象(递归地)
/// 
/// 要被序列化的对象
/// 当前正在被序列化的对象在序列化树中的层级(根级为0)
/// 当前正在被序列化的对象的变量名
/// 
///     该方法有四个重载:
///     1. serializeObject(o);
///     2. serializeObject(o, level);
///     3. serializeObject(o, varName);
///     4. serializeObject(o, level, varName);
/// 
function serializeObject (){            
    // 参数列表:
    var o = arguments[0];
    var level = 0;          
    var varName = "";     
// 重载机制:
switch(typeof arguments[1]){
    case "number":
        // 重载 2: serializeObject(o, level);
        level = arguments[1];
        if(arguments.length > 2) {
            // 重载 4: serializeObject(o, level, varName);
            varName = arguments[2];
        }
        break;
    case "string":
        // 重载 3: serializeObject(o, varName);
        varName = arguments[1];
        break;
    default:
        // 重载 1: serializeObject(o);
        break;
}

var sb = new StringBuffer();
// 根对象信息:
sb.appendLine("{0}{1}<{2}>: [{3}]".format(level > 0 ? " ".duplicate(level*2) + "|-" : "", varName, typeof o, o === null ? "null" : o === undefined ? "undefined" : o.toString()));
// 子对象信息:
switch(typeof o) {
    case "object":
        for(var i in o){
            //sb.appendLine("{0}{1}<{2}>: [{3}]".format(" ".duplicate((level+1)*2), i, typeof o[i], o[i] === null ? "null" : o[i] === undefined ? "undefined" : o[i].toString()));
            sb.append(serializeObject(o[i], level + 1, i));
        } // end for
        break;
    case "undefined":
        break;
    default:
        // 根对象的prototype信息:
        switch(typeof o.prototype){
            case "undefined":
                break;
            default:
                sb.append(serializeObject(o.prototype, level + 1, "prototype"));
                break;
        } // end switch (typeof o.prototype)
        break;
} // end switch (typeof o)
return sb.toString();

}

四、测试:

对于上述的这个函数,我们可以根据通过如下方式去调用:

var o = {
	a: 2,
	b: 3,
	c: {
		prop1: "abc",
		prop2: "bcd",
		prop3: {
			key1: "key1",
			key2: "key2"
		},
		prop4: "cde"
	},
	d: "Hello"
};

alert(serializeObject(o));
alert(serializeObject(o, 4));
alert(serializeObject(o, "o"));
alert(serializeObject(o, 4, "o"));

这样,就避免了这样的情况:

alert(serializeObject(o));
alert(serializeObjectWithInitialLevel(o, 4));
alert(serializeObjectWithVarName(o, "o"));
alert(serializeObjectWithInitialLevelAndVarName(o, 4, "o"));

可见,重载方案要优雅得多!

注意:在这个示例重载函数serializeObject()中,使用了StringBuffer对象,String对象的format()和duplicate()方法,它们不是JavaScript内置的,它们的介绍分别见:

以上测试代码的运行的第一个结果截图如下:

JavaScript重载机制测试结果

完整的可运行的测试代码为:

//
// String Buffer Class
//
function StringBuffer() {
    this.__strings__ = new Array();

    if (typeof StringBuffer._initialized == "undefined") {
        StringBuffer.prototype.append = function (s) {
            this.__strings__.push(s);
        };

        StringBuffer.prototype.appendLine = function (s) {
            this.__strings__.push(s + "\n");
        };

        StringBuffer.prototype.toString = function () {
            return this.__strings__.join("");
        };

        StringBuffer._initialized = true;
    }
}

String.prototype.format = function () {
    //        return String.format.apply(arguments);
    var string = this;
    for (var i = 0; i < arguments.length; i++) {
        string = string.replace("{" + i + "}", arguments[i]);
    }

    return string;
};

/// 
///     复制字符串为原来的n倍
/// 
String.prototype.duplicate = function (n) {
    var sb = new StringBuffer();
    for (var i = 0; i < n; i++) {
        sb.append(this);
    }

    return sb.toString();
};

/// 
///     序列化一个对象(递归地)
/// 
/// 要被序列化的对象
/// 当前正在被序列化的对象在序列化树中的层级(根级为0)
/// 当前正在被序列化的对象的变量名
/// 
///     该方法有四个重载:
///     1. serializeObject(o);
///     2. serializeObject(o, level);
///     3. serializeObject(o, varName);
///     4. serializeObject(o, level, varName);
/// 
function serializeObject() {
    // 参数列表:
    var o = arguments[0];
    var level = 0;
    var varName = "";

    // 重载机制:
    switch (typeof arguments[1]) {
        case "number":
            // 重载 2: serializeObject(o, level);
            level = arguments[1];
            if (arguments.length > 2) {
                // 重载 4: serializeObject(o, level, varName);
                varName = arguments[2];
            }
            break;
        case "string":
            // 重载 3: serializeObject(o, varName);
            varName = arguments[1];
            break;
        default:
            // 重载 1: serializeObject(o);
            break;
    }

    var sb = new StringBuffer();
    // 根对象信息:
    sb.appendLine("{0}{1}<{2}>: [{3}]".format(level > 0 ? " ".duplicate(level * 2) + "|-" : "", varName, typeof o, o === null ? "null" : o === undefined ? "undefined" : o.toString()));
    // 子对象信息:
    switch (typeof o) {
        case "object":
            for (var i in o) {
                //sb.appendLine("{0}{1}<{2}>: [{3}]".format(" ".duplicate((level+1)*2), i, typeof o[i], o[i] === null ? "null" : o[i] === undefined ? "undefined" : o[i].toString()));
                sb.append(serializeObject(o[i], level + 1, i));
            } // end for
            break;
        case "undefined":
            break;
        default:
            // 根对象的prototype信息:
            switch (typeof o.prototype) {
                case "undefined":
                    break;
                default:
                    sb.append(serializeObject(o.prototype, level + 1, "prototype"));
                    break;
            } // end switch (typeof o.prototype)
            break;
    } // end switch (typeof o)
    return sb.toString();
};

var o = {
    a: 2,
    b: 3,
    c: {
        prop1: "abc",
        prop2: "bcd",
        prop3: {
            key1: "key1",
            key2: "key2"
        },
        prop4: "cde"
    },
    d: "Hello"
};

alert(serializeObject(o));
alert(serializeObject(o, 4));
alert(serializeObject(o, "o"));
alert(serializeObject(o, 4, "o"));