本节书摘来华章计算机出版社《JavaScript应用程序设计》一书中的第2章,第2.11节,作者:Eric Elliott 更多章节内容可以访问云栖社区“华章计算机”公众号查看。
2.11 多态函数
在计算机学科中,多态性意味一件事物的行为取决于它当前所处的上下文环境,就像单词一样,在不同的句子中的含义也不尽相同,如下例中“东西”一词。
· 迈尔斯是韩国东西大学专门研究北韩官方宣传与传播的教授。
· 据谣传这所房间每天夜里都有什么东西吼叫。
· 这是个交通枢纽,铁路由此向东西南北伸展出去。
同理,多态函数意味着函数在执行期间的行为由传入的具体参数决定,在JavaScript中,这些参数存储在一个被称为arguments的类数组对象中,说它是类数组是因为它本身不具备Array类的实例方法。
使用Array.prototype.slice()可以对数或类数组对象进行浅拷贝操作。
你可以从Array类的原型对象上“借用”slice()方法,这种方法借用机制,我们称之为“方法代理”,调用方式如下:
var args = Array.prototype.slice.call(arguments, 0);
这将返回一个真正意义上的数据,不过这种语法看起来有一些冗余,我们换一种写法:
var args = [].slice.call(arguments, 0);
我们使用方括号语法创建了一个用来被slice()方法代理的空数组对象,这听上去感觉会对性能造成一定损耗,不过对JavaScript引擎来说创建空数组是一件极为迅速的操作,根据反复性能测试的数据结果来看,这项操作对内存与性能的影响是微乎其微的。
你可以使用这种方法创建一个对入参进行排序的函数:
function sort() {
var args = [].slice.call(arguments, 0);
return args.sort();
}
test('Sort', function () {
var result = sort('b', 'a', 'c');
ok(result, ['a', 'b', 'c'], 'Sort works!');
});
因为arguments不是一个真正意义上的数组,所以它没有sort()方法。不过你可以通过slice()方法将arguments对象转变为一个真数组,之后再调用数组方法sort()返回一个经过排序的数组。
多态函数需要判断第一个参数的类型来决定后续的执行流程。现在args已经是数组,可以使用shift()方法获取第一个参数:
var first = args.shift();
若第一个参数类型是String,则进入分支流程:
function morph(options) {
var args = [].slice.call(arguments, 0),
animals = 'turtles'; // Set a default
if (typeof options === 'string') {
animals = options;
args.shift();
}
return('The pet store has ' + args + ' ' + animals
+ '.');
}
test('Polymorphic branching.', function () {
var test1 = morph('cats', 3),
test2 = morph('dogs', 4),
test3 = morph(2);
equal(test1, 'The pet store has 3 cats.', '3 Cats.');
equal(test2, 'The pet store has 4 dogs.', '4 Dogs.');
equal(test3, 'The pet store has 2 turtles.',
'The pet store has 2 turtles.');
});
方法调度
方法调度是指对象在接收到外界的消息时再决定其具体行为。在JavaScript中,接受消息的对象先检查自己是否有方法,如果没有,则查找原型对象,如果还没有,则查找父级原型对象,如此往复,直到找到匹配的方法随后将参数传入并调用,这在JavaScript这种基于原型的语言中被称为行为代理。
在程序运行期间根据入参的类型选择合适的函数执行,这种多态性我们称为动态调度。某些语言对动态调度有特殊语法支持,而在 JavaScript中的一般实现是,在一个方法中获取入参,随后根据入参决定另一个方法的调用。
var methods = {
init: function (args) {
return 'initializing...';
},
hello: function (args) {
return 'Hello, ' + args;
},
goodbye: function (args) {
return 'Goodbye, cruel ' + args;
}
},
greet = function greet(options) {
var args = [].slice.call(arguments, 0),
initialized = false,
action = 'init'; // init will run by default
if (typeof options === 'string' &&
typeof methods[options] === 'function') {
action = options;
args.shift();
}
return methods[action](args);
};
test('Dynamic dispatch', function () {
var test1 = greet(),
test2 = greet('hello', 'world!'),
test3 = greet('goodbye', 'world!');
equal(test2, 'Hello, world!',
'Dispatched to hello method.');
equal(test3, 'Goodbye, cruel world!',
'Dispatched to goodbye method.');
});
动态调度机制在jQuery的插件中较为常见,解决了开发人员为了增加插件中的方法而去污染jQuery原型链的问题。利用动态调度,你可以先在jQuery原型链中注册一个插件名称,随后就可以在此名称上任意添加你所需要的方法,插件使用者则通过下列方式调用你的方法。
$(selection).yourPlugin('methodName', params);