首先,我們看兩個函數
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的源碼,确實,和我的思路是一樣的。