Javascript 奇異的 arguments

在 Javascript 的函數中有個名爲 arguments 的類數組對象。它看起來是那麼的詭異而且名不經傳,但衆多的 Javascript 庫都使用着它強大的功能。所以,它的特性需要每個 Javascript 程序員去熟悉它。

在每個函數中,都有個名爲 arguments 的變量,它以類似數組的形式保存了當前調用的參數。而它實際上並不是個數組,使用 typeof arguments 語句嘗試會返回“object”(對象),所以它不能像 Array 一樣使用 push 和 pop 等方法。即便如此,仍然可以使用下標以及長度屬性(length)獲取它的值。

編寫靈活的函數

雖看起來名不經傳,但的確 arguments 是非常有用的對象。比如,你可以讓函數處理不定數目的參數。在 Dean Edwards 寫的 base2 庫中,有個叫 format 的函數充分發揮了這一特性:

  1. function format(string) {
  2.   var args = arguments;
  3.   var pattern = new RegExp("%([1-" + arguments.length + "])""g");
  4.   return String(string).replace(pattern, function(match, index) {
  5.     return args[index];
  6.   });
  7. };

這個函數實現了模板替換,你可以在要動態替換的地方使用 %1 到 %9 標記,然後其餘的參數就會依次替換這些地方。例如:

  1. format("And the %1 want to know whose %2 you %3""papers""shirt""wear");

上面的腳本就會返回

"And the papers want to know whose shirt you wear" 。

在這裏需要注意的是,即便在 format 函數定義中,我們僅定義了個名爲 string 的參數。而 Javascript 不管函數自身定義的參數數量,它都允許我們向一個函數傳遞任意數量的參數,並將這些參數值保存到被調用函數的 arguments 對象中。

轉換成實際數組

雖然 arguments 對象並不是真正意義上的 Javascript 數組,但是我們可以使用數組的 slice 方法將其轉換成數組,類似下面的代碼

  1. var args = Array.prototype.slice.call(arguments);

這樣,數組變量 args 包含了所有 arguments 對象包含的值。

創建預置參數的函數

使用 arguments 對象能夠簡短我們編寫的 Javascript 代碼量。下面有個名爲 makeFunc 的函數,它根據你提供的函數名稱以及其他任意數目的參數,然後返回個匿名函數。此匿名函數被調用時,合併的原先被調用的參數,並交給指定的函數運行然後返回其返回值。

  1. function makeFunc() {
  2.   var args = Array.prototype.slice.call(arguments);
  3.   var func = args.shift();
  4.   return function() {
  5.     return func.apply(null, args.concat(Array.prototype.slice.call(arguments)));
  6.   };
  7. }

makeFunc 的第一個參數指定需要調用的函數名稱(是的,在這個簡單的例子中沒有錯誤檢查),獲取以後從 args 中刪除。makeFunc 返回一個匿名函數,它使用函數對象的(Function Object)apply 方法調用指定的函數。

apply 方法的第一個參數指定了作用域,基本上的作用域是被調用的函數。不過這樣在這個例子中看起來會有點複雜,所以我們將其設定成 null ;其第二個參數是個數組,它指定了其調用函數的參數。makeFunc 轉換其自身的 arguments 並連接匿名函數的 arguments,然後傳遞到被調用的函數。

有種情況就是總是要有個輸出的模板是相同的,爲了節省每次是使用上面提到的 format 函數並指定重複的參數,我們可以使用 makeFunc 這個工具。它將返回一個匿名函數,並自動生成已經指定模板後的內容:

  1. var majorTom = makeFunc(format, "This is Major Tom to ground control. I'm %1.");

你可以像這樣重複指定 majorTom 函數:

  1. majorTom("stepping through the door");
  2. majorTom("floating in a most peculiar way");

那麼當每次調用 majorTom 函數時,它都會使用第一個指定的參數填寫已經指定的模板。例如上述的代碼返回:

"This is Major Tom to ground control. I'm stepping through the door."
"This is Major Tom to ground control. I'm floating in a most peculiar way."

自引用的函數

您可能會認爲這很酷,先別急着高興,後面還有個更大的驚喜。它(arguments)還有個其他非常有用的屬性:callee 。arguments.callee 包含了當前調用函數的被引用對象。那麼我們如何使用這玩意做些的事情?arguments.callee 是個非常有用的調用自身的匿名函數。

下面有個名爲 repeat 的函數,它的參數需要個函數引用和兩個數字。第一個數字表示運行的次數,而第二個函數定義運行的間隔時間(毫秒爲單位)。下面是相關的代碼:

  1. function repeat(fn, times, delay) {
  2.   return function() {
  3.     if(times-- > 0) {
  4.       fn.apply(null, arguments);
  5.       var args = Array.prototype.slice.call(arguments);
  6.       var self = arguments.callee;
  7.       setTimeout(function(){self.apply(null,args)}, delay);
  8.     }
  9.   };
  10. }

repeat 函數使用 arguments.callee 獲得當前引用,保存到 self 變量後,返回個匿名函數重新運行原本被調用的函數。最後使用 setTimeout 以及配合個匿名函數實現延遲執行。

作爲個簡單的說明,比如會在通常的腳本中,編寫下面的提供個字符串並彈出個警告框的簡單函數:

  1. function comms(s) {
  2.   alert(s);
  3. }

好了,後來我改變了我的想法。我想編寫個“特殊版本”的函數,它會重複三次運行每次間隔兩秒。那麼使用我的 repeat 函數,就可以像這樣做到:

  1. var somethingWrong = repeat(comms, 3, 2000);
  2. somethingWrong("Can you hear me, major tom?");

結果就猶如預期的那樣,彈出了三次警告框每次延時兩秒。

最後,arguments 即便不會經常被用到,甚至顯得有些詭異,但是它上述的那些驚豔的功能(不僅僅是這些!)值得你去了解它。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章