# 8元編程
### 檢測與構建丟失的函數
### 問題
你想要檢測一個函數是否存在,如果不存在則構建該函數。(比如 Internet Explorer 8 的 ECMAScript 5 函數)。
### 解決方案
使用存在賦值運算符(?=)來把函數分配給類庫的原型(使用 :: 簡寫),然后把它放于一個立即執行函數表達式中(do ->)使其含有所有變量。
~~~
do -> Array::filter ?= (callback) ->
element for element in this when callback element
?
array = [1..10]
?
array.filter (x) -> x > 5
# => [6,7,8,9,10]
~~~
### 討論
在 JavaScript (同樣地,在 CoffeeScript)中,對象都有一個原型成員,它定義了什么成員函數能夠適用于基于該原型的所有對象。
在CoffeeScript中,你可以使用 :: 捷徑來訪問這個原型。所以如果你想要把過濾函數添加至數組類中,就執行 **Array::filter = ...** 語句。它能把過濾函數加至所有數組中。
但是,不要去覆蓋一個在第一時間還沒有構造的原型。比如,如果 **Array::filter = ...** 已經以快速本地形式存在于瀏覽器中,或者庫制造者擁有其對于 **Array::filter = ...** 的獨特版本,這樣以來,你要么換一個慢速的 JavaScript 版本,要么打破這種依賴于其自身 Array::shuffle 的庫。
你需要做的僅僅是在函數不存在的時候添加該函數。這就是存在賦值運算符(?=)的意義。如果我們執行 **Array::filter = ...** 語句,它會首先判斷 **Array::filter** 是否已經存在。如果存在的話,它就會使用現在的版本。否則,它會添加你的版本。
最后,由于存在的賦值運算符在編譯時會創建一些變量,我們會通過把它們封裝在[立即調用函數表達式( IIFE )](http://benalman.com/news/2010/11/immediately-invoked-function-expression/)中來簡化代碼。這將隱藏那些內部專用的變量,以防止泄露。所以假如我們寫的函數已經存在,那么它將運行,基本上什么都沒做然后退出,絕對不會對你的代碼造成影響。但是假如我們寫的函數并不存在,那么我們發送出去的僅是一個作為閉包的函數。所以只有你寫的函數能夠對代碼產生影響。無論哪種方式,?= 的內部運行都會被隱藏。
### 舉例
接下來,我們用上述的方法編譯了 CoffeeScript 并附加了說明:
~~~
// (function(){ ... })() 是一個 IIFE, 使用 `do ->` 來編譯它。
(function() {
?
// 它來自 `?=` 運算符,用來檢查 Array.prototype.filter (`Array::filter`) 是否存在。
// 如果確實存在,我們把它設置給其自身,并返回。如果不存在,則把它設置給函數,并返回函數。
// The IIFE is only used to hide _base and _ref from the outside world.
var _base, _ref;
return (_ref = (_base = Array.prototype).filter) != null ? _ref : _base.filter = function(callback) {
?
// `element for element in this when callback element`
var element, _i, _len, _results;
_results = [];
for (_i = 0, _len = this.length; _i < _len; _i++) {
element = this[_i];
if (callback(element)) {
_results.push(element);
}
}
return _results;
?
};
// The end of the IIFE from `do ->`
})();
~~~
### 擴展內置對象
### 問題
你想要擴展一個類來增加新的函數或者替換舊的。
### 解決方案
使用 :: 把你的新函數分配到對象或者類的原型中。
~~~
String::capitalize = () ->
(this.split(/\s+/).map (word) -> word[0].toUpperCase() + word[1..-1].toLowerCase()).join ' '
?
"foo bar baz".capitalize()
# => 'Foo Bar Baz'
~~~
### 討論
在 JavaScript (同樣地,在 CoffeeScript )中,對象都有一個原型成員,它定義了什么成員函數能夠適用于基于該原型的所有對象。在 CoffeeScript 中,你可以使用 :: 捷徑來直接訪問這個原型。
> 注意:雖然這種做法在很多種語言中相當普遍,比如 Ruby,但是在 JavaScript 中,擴展本地對象通常被認為是不好的做法(可參考:[可維護的 JavaScript:不要修改你不擁有的對象](http://www.nczonline.net/blog/2010/03/02/maintainable-javascript-dont-modify-objects-you-down-own/);[擴展內置的本地對象。對還是錯?](http://perfectionkills.com/extending-native-builtins/)。)