首先,我们看两个函数
var perform1 = function (dog) {
console.log(dog.name);
};
var perform2 = function (cat) {
console.log(cat.name);
};
这两个函数很难想象他们调用会有不一样的效果,他们唯一的区别就是参数名称的不同。然而很多流行的框架,你传入的匿名函数,你自己都不知道参数应该是什么,什么顺序,所以在大部分框架中就实现了根据参数名来赋值的效果。
比如说,我们有一个Performance类,有个perform方法,而这个perform方法让用户自己定义。也就是说,用户需要自己定义个函数,然后赋值给Performance.perform.用户想实现如下的功能:
var performance = new Performance();
performance.perform = function (dog) {
console.log(dog.name);
};
performance.do();
performance.perform = function (cat) {
console.log(cat.name);
};
performance.do();
performance.perform = function (cat, dog) {
console.log(cat.name + " " + dog.name);
};
performance.do();
用户不用在乎参数的顺序,只需要提供一定格式的参数名,比如我用cat做参数名,那请给我一个cat的对象,用dog就给我dog对象,如果参数cat dog都有,那请给我两个对象。
可能你会觉得这种需求还是不怎么明白,那我举个大家熟悉的例子
jQuery.ajax({
data: {},
success:function(data,state){
},
error: function (xhr,msg,obj) {
}
});
这是JQuery的ajax方法,它其中的sucess和error是需要用户自己定义的函数,但是函数的参数是固定死的,也就是说在error函数中,第一个参数就是xhr对象,第二个参数就是错误信息,用户不能颠倒顺序,或者漏写前面某个参数而去使用后面的参数。比如,我想直接弹出错误信息:
error: function (msg) {
alert(msg);
}
可是实际结果是弹出xhr实例的toString()结果。
有人可能会说,这是常识吧,所有程序都是这样执行的啊。
实际上,用过Angular的人都会发现,在使用Angular的过程中,传参从来不用考虑第一个参数应该是什么。$http参数放在第一个或者第二个它都是$http实例,用户在factory中定义了一堆service,也只需要通过参数名就能获取到,实际上这种体验要明显好很多。
然而怎么去实现呢?第一点就是怎么获取参数名称。其实javascript的每个函数都有toString()方法,我们可以从这个方法入手。
args = fn.substring(fn.indexOf('(') + 1, fn.indexOf(')')).split(',');
当我们得到函数toString()后的字符串fn后,就可以通过上面截取字符串的方式获得参数名集合。
比如,我们拿到的参数里有名称为dog的参数,那么我们就取this["dog"]从而拿到dog对象,用它当参数去调用函数。
下面代码就是实现文章一开始提到dog还是cat得问题。
var Performance = function () {
this.dog = { name: 'dog' };
this.cat = { name: 'cat' };
this.do = function () {
var fn = this.perform.toString(),
args = fn.substring(fn.indexOf('(') + 1, fn.indexOf(')')).split(',');
for (var i = 0; i < args.length ; i++) {
args[i] = this[args[i].trim()];
}
this.perform.apply(this, args);
}
};
一开始我比较纠结如何将一个数组当参数传递给一个方法,后来想起了apply方法,这个方法本质上是为了替换一个函数中this的实例的,但是它第二个参数可以接受一个参数数组,这便是我们这里需要的。
同时,我也查看了Angular的源码,确实,和我的思路是一样的。