再探 JavaScript的中介編程 foreach
網友 WanCW 在 JavaScript的中介編程與反射能力示範 一文中回應 文章中的 foreach() 並未產生新的程式或是修改現有的程式,好像不太能算是 metaprogramming?
並非如此,其實 foreach 在中介編程(metaprogramming)的領域是經典樣式。只是我上文的例子太精簡,以至於看不出它的威力。嗯,如果不來個複雜點的程式碼,確實不容易看出 foreach 到底可以幫我們省下多少程式碼。我就來個複雜點的示範吧。
傳統迭代控制結構
首先,我先寫一段傳統的程式碼。我分別配置了一個陣列a和一個 XML 文件 x。接著用for(;;){}
迭代控制結構去傾印它們的內容。
本文範例中使用了 Document 資料型別,而這是以網頁瀏覽器為宿主時才會擁有的原生型別,所以請透過瀏覽器(如 Firefox, IE)等執行。如果不知道如何執行的讀者,可以開啟 JavaScript shell 此網頁,把程式碼剪貼到上面去執行。
如無意外,你會看到兩次 01234。這分別是 a 和 x 的傾印結果。
經驗豐富的程序員知道這種 for(;;){}
迭代控制結構在程式中的重複頻率有多高。
不論你的迭代內容是要做什麼,只要你想從頭到尾走訪一遍,你就要重複輸入一次for(;;){}
。
抽出迭代控制結構,化為 foreach
遵循 DRY (程序員天生是懶人) 原則,這種重複的程式碼就要抽離出來。
我分別定義了兩個建構者(Java叫"類別"), DataArray 負責陣列,DataXml 負責 XML 文件。同樣都定義了 foreach 方法。而它們的內容其實就是上面的 for(;;){}
,我把它們搬進類別中了。
至於迭代內容則以函數加以參數化,傳給 foreach 調用。
使用 foreach 取代迭代控制結構
接下來就是重頭戲了,
瞧,一整個簡化了,你不用再看見重複的 for(;;){}
了。甚至不用再區分 array 或 xml 而寫不同的程式碼。
好戲就這樣嗎?不,還沒呢。精彩的總是壓軸演出。
有沒有添加 foreach 語法的差異性有多大,現在是一目瞭然。
8.8 Custom Control Structures
Ruby’s use of blocks, coupled with its parentheses-optional syntax, make it very easy to define iterator methods that look like and behave like control structures.
Flanagan and Matz, The Ruby Programming Language, page 281, O'Reilly 2008
在 JavaScript 中,自定的 foreach 可以讓我們把重複的迭代控制碼 for(;;){}
寫在個體方法中。我們只需要將處理元素內容的迭代器(包在 for {} 區塊中的那些程式碼),透過一般函數或匿名函數加以參數化傳給 foreach ,它就自動幫我們做好迭代控制工作了。
看看上面的例子,我們在使用 foreach 後省下了一大堆程式碼,這正是 metaprogramming 。
如果覺得上述的示範還是不算中介編程,那麼不妨換個方式來看本文的例子。請把 foreach 看成一段 macro,而不要看成一個函數。那麼當我寫下 d.foreach( pv );
之後,各位就可以想像成 JavaScript 自動把上面那一行代換成(產生)
for (xxx;xxx;xxx) { v = i; pv(v) }
當然我上面的例子中顯示,JavaScript這個 "foreach" 巨集還具有判斷對象型別套用不同for(;;){}
的功能。在 C++ 中,這是用 template 來做的.
One style of programming which focuses heavily on metaprogramming is language-oriented programming, which is done via domain-specific programming languages.
metaprogramming@Wikipedia
除了macro或eval的方式,其實 JavaScript 更傾向於透過 DSL 的方式去實踐中介編程。用 JavaScript 去加強 JavaScript 自己(不透過其他語言),改變了 JavaScript 原本的編程風格。如果你做的夠多,甚至可以讓你以後寫出完全不像 JavaScript 語法的 JavaScript 程式碼。
樂多舊回應