如果你仔細看了到目前為止出現過的示例代碼,你會發現這裡面的一些方法不太熟悉。 它們是map()、filter()和reduce()函數,它們對任何語言的函數式編程都至關重要。 它們可以讓你不必使用循環和語句,寫出更簡潔的代碼。
map()、filter()和reduce()函數組成了函數式程序員工具集的核心部分,這個工具集包括一系列純的、 高階的函數,它們是函數式方法的主力。實際上,它們是純函數和高階函數的典型,它們以一個函數為輸入, 返回一個輸出結果,並且不產生副作用。
然而它們是浏覽器中ECMAScript 5.1的實現標准,它們只工作於數組。每次調用它們,一個新的數組會被創建並返回, 而原來存在的那個數組不會被改變。它們以函數為輸入,經常使用匿名函數作為回調函數。它們遍歷數組, 並對數組的每一個元素應用這個函數!
myArray = [1,2,3,4]; newArray = myArray.map(function(x) {return x*2}); console.log(myArray); // Output: [1,2,3,4] console.log(newArray); // Output: [2,4,6,8]
還有一點,它們只作用於數組,無法作用於其它可迭代的數據結構,比如對象。不用擔心, 有很多庫比如Underscore.js,Lazy.js,stream.js等等都實現了它們自己的更強大的map()、 filter()和reduce()。
回調
如果你以前從來沒用過回調,那這個概念可能會讓你有些迷惑。尤其是在Javascript中, Javascript給出了好幾種聲明函數的方式。
回調函數用於傳遞給另外一個函數供它們使用,這是一種像傳遞對象一樣來傳遞邏輯的方式:
var myArray = [1,2,3]; function myCallback(x){return x+1}; console.log(myArray.map(myCallback));
對於比較簡單的任務可以用匿名函數:
console.log(myArray.map(function(x){return x+1}));
回調不僅用於函數式編程,在Javascript中它們能干很多事情。僅作為例子,這有個callback()函數用於jQuery的AJAX調用:
function myCallback(xhr) { console.log(xhr.status); return true; } $.ajax(myURI).done(myCallback);
注意這裡只用了函數的名字,因為我們並不是要調用函數而是傳遞函數,寫成這樣就錯了:
$.ajax(myURI).fail(myCallback(xhr)); // 或者 $.ajax(myURI).fail(myCallback());
如果我們調用了函數會發生什麼?在這個例子裡,myCallback(xhr)會嘗試執行,控制台將打印“undefined”, 並會返回true。當ajax()完成調用時,它根據名字找到的回調函數將是一個"true",然後就報錯了。
也就是說我們無法指定給回調函數傳什麼參數,如果我們的回調函數需要讓ajax()函數傳給他我們想要的參數, 我們可以把回到函數包在一個匿名函數裡:
function myCallback(status) { console.log(status); return true; } $.ajax(myURI).done(function(xhr) { myCallback(xhr.status) });
Array.prototype.map()
map()是這些函數的老大,它簡單地對數組裡的元素依此應用回調函數。
語法:arr.map(callback [, thisArg]);
參數:
•callback(): 這個函數為新數組產生一個元素,它接收的參數: ◦currentValue:數組當前遍歷到的元素
◦index:數組中當前元素序數
◦array:當前正在處理的數組
•thisArg:這是個可選參數,當執行回調的時候它作為回調函數的this
例子:
var integers = [1, -0, 9, -8, 3], numbers = [1, 2, 3, 4], str = 'hello world how ya doing?'; // 將整數映射為他們自己的絕對值 console.log(integers.map(Math.abs)); // 將數組中的元素與自己的位置序數相乘 console.log(numbers.map(function(x, i) { return x * i })); // 單詞隔一個變一個大寫 console.log(str.split(' ').map(function(s, i) { if (i % 2 == 0) return s.toUpperCase(); else return s; }));
盡管Array.prototype.map方法是Javascript中數組對象的標准方法,你也可以很容易地擴展自己的對象。
MyObject.prototype.map = function(f) { return new MyObject(f(this.value)); };
Array.prototype.filter()
filter()函數用於把數組中的一些元素篩選出來。回調函數必須返回真(保留到新數組裡)或假(扔掉)。 用map()可以做類似的事情,就是把你像扔掉的元素返回為null,不過filter()函數會在新數組裡面刪除這些不要的元素, 而不是留個null占著位置。
語法:arr.filter(callback [, thisArg]);
•callback():這個函數用來測試數組中的每個元素,要保留返回真,否則返回假。它有這些參數: ◦currentValue:數組當前遍歷到的元素
◦index:數組中當前元素的序數
◦array:當前正在處理的數組
•thisArg:這是個可選參數,當執行回調的時候它作為回調函數的this
例子:
var myarray = [1, 2, 3, 4] words = 'hello 123 world how 345 ya doing'.split(' '); re = '[a-zA-Z]'; // 篩選整數 console.log([-2, -1, 0, 1, 2].filter(function(x) { return x > 0 })); // 篩選所有含字母的單詞 console.log(words.filter(function(s) { return s.match(re); })); // 隨機移除數組中的元素 console.log(myarray.filter(function() { return Math.floor(Math.random() * 2) }));
Array.prototype.reduce()
reduce()函數,有時也稱為fold,它用於把數組中的所有值聚集到一起。回調需要返回組合對象的邏輯。 對於數字來說,它們往往會被加到一起或者乘到一起。對於字符串來說,它們往往是被追加到一起。
語法:arr.reduce(callback [, initialValue]);
參數
•callback():此函數把兩個對象合並成一個對象,並將其返回。參數有: ◦previousValue:上一次回調函數被調用時返回的值,或者是初始值(如果有的話)
◦currentValue:數組當前正在處理的元素
◦index:數組中當前元素的序數
◦array:當前正在處理的數組
•initialValue:可選。第一次回調所傳入參數的初始值
例子
var numbers = [1, 2, 3, 4]; // 把數組中所有的值加起來 console.log([1, 2, 3, 4, 5].reduce(function(x, y) { return x + y }, 0)); // 查找數組中最大的值 console.log(numbers.reduce(function(a, b) { return Math.max(a, b) // max()函數只能有兩個參數 }) );
其它函數
map()、filter()和reduce()函數在我們輔助函數的工具箱裡並不孤單。這裡還有更多的函數幾乎在所有函數式應用裡都會被使用。
Array.prototype.forEach
forEach()函數本質上是map()函數的非純版本,它會遍歷整個數組,並對每個元素應用回調。 然而這些回調函數不返回值。它是實現for循環的一個更純粹的方式。
語法:arr.forEach(callback [, thisArg]);
參數:
•callback():對數組中每一個元素所應用的。參數有: ◦currentValue:數組中當前正在處理的元素
◦index:數組中當前元素的序數
◦array:正在處理的數組
•thisArg:可選。回調函數中作為this的值
例子:
var arr = [1, 2, 3]; var nodes = arr.map(function(x) { var elem = document.createElement("div"); elem.textContent = x; return elem; }); // 對每一個元素的值輸出日志 arr.forEach(function(x) { console.log(x) }); // 把節點追加到DOM上 nodes.forEach(function(x) { document.body.appendChild(x) });
Array.prototype.concat
如果不用for或while處理數組,你會經常需要把數組拼接起來。另一個Javascript內建函數concat就是專門干這事兒的。 concat函數會返回一個新數組但不改變舊數組。它可以把你傳入的所有參數拼接到一起。
console.log([1, 2, 3].concat(['a','b','c']) // 拼接兩個數組
// Output: [1, 2, 3, 'a','b','c']
它返回兩個數組拼接成的數組,同時原來的那些數組沒有被改變。這就意味著concat函數可以鏈式調用。
var arr1 = [1,2,3]; var arr2 = [4,5,6]; var arr3 = [7,8,9]; var x = arr1.concat(arr2, arr3); var y = arr1.concat(arr2).concat(arr3)); var z = arr1.concat(arr2.concat(arr3))); console.log(x); console.log(y); console.log(z);
變量x、y、z的值最後都是[1,2,3,4,5,6,7,8,9]。
Array.prototype.reverse
這個Javascript內建函數是用於數組變形的。reverse函數用於將一個數組反轉,也就是第個一元素會跑到最後, 而最後一個元素變成了第一個元素。
然而,這個函數並不會返回一個新的數組,而是把原來的數組替換掉了。我們可以做個更好的。下面是一個純的反轉數組函數
var invert = function(arr) { return arr.map(function(x, i, a) { return a[a.length - (i + 1)]; }); }; var q = invert([1, 2, 3, 4]); console.log(q);
Array.prototype.sort
與map()、filter()和reduce()函數相似,排序函數sort()需要傳入一個回調函數來定義數組如何排序。 但是,跟reverse()一樣,它也會把原來的數組替換。這可不太好。
arr = [200, 12, 56, 7, 344];
console.log(arr.sort(function(a,b){return a–b}) );
// arr現在是: [7, 12, 56, 200, 344];
我們可以寫一個純函數的sort(),但是排序算法的源代碼很麻煩。對於特別大的數組,應當根據特定的數據結構來選用適合的算法, 比如快速排序、合並排序、冒泡排序等等。
Array.prototype.every 和 Array.prototype.some
Array.prototype.every() 和 Array.prototype.some() 都是純的高階函數,它們是Array對象的方法, 通過回調函數根據數組各元素返回的布爾值(或相當於布爾的值)來進行測試。如果數組中所有的元素通過回調函數計算都返回True, every()函數就返回true;如果數組中有一個元素返回True,some()函數就返回True。
例子:
function isNumber(n) { return !isNaN(parseFloat(n)) && isFinite(n); } console.log([1, 2, 3, 4].every(isNumber)); // Return: true console.log([1, 2, 'a'].every(isNumber)); // Return: false console.log([1, 2, 'a'].some(isNumber)); // Return: true