一、背景:
在面向对象的编程语言中,通过重载机制,使得同一个方法名可以具有不同的实现,这些不同的实现版本具有不同的参数(个数、类型都可以不同)。这些不同的参数形成了方法的不同的特征(或者叫签名),从而在使用中,即使方法名相同,程序也能正确地找到对应的版本。
在JavaScript中,没有内置的重载机制,但是它对每个方法(函数)都提供了一个arguments对象,该对象具有传递过来的参数信息,我们可以利用它来实现(或者说模拟)重载机制。
为什么要写多余的代码来实现这个重载机制呢?其实一般不需要,但是在某些情况下,多加一点点代码来实现重载机制后,带来的是巨大的灵活性和可重用性。否则给做同样事情的方法或函数,仅仅因为传进来的参数稍有不同就起不同的名字,会显得非常冗余、奇怪和不容易记忆。
二、分析:
在内置重载机制的语言里,编译器要对函数的参数列表进行两种分析:
- 参数个数:对相同名字的方法(或者说函数),不同的参数个数对应不同的实现版本;
- 参数的类型:参数的个数相同,但是处于同一位置的参数类型不一样,那么也能区分出它们的不同版本。
在JavaScript中,没有内置这样的机制,我们可以在代码中用同样的方法自己实现,基本思路是也是如此:
- 参数个数:通过调用arguments.length,对不同个数的参数给予不同的实现;
- 参数的类型:通过 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内置的,它们的介绍分别见:
以上测试代码的运行的第一个结果截图如下:
完整的可运行的测试代码为:
//
// 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"));