一道經典面試題:javascript
//JS實現一個無限累加的add函數 add(1) //1 add(1)(2) //3 add(1)(2)(3) //6
當你們看到這個面試題的時候,可否在第一時間想到使用高階函數實現?想到在實際項目開發過程當中,用到哪些高級函數?有沒有想過本身創造一個高階函數呢?開始本篇文章的學習java
文章已同步都github博客地址:
程序員成長指北技術棧博客地址node
高階函數
英文叫 Higher-order function
。高階函數是對其餘函數進行操做的函數,操做能夠是將它們做爲參數,或者返回它們。簡單總結爲高階函數是一個接收函數做爲參數
或者將函數做爲返回輸出
的函數。git
Array.prototype.map
,Array.prototype.filter
,Array.prototype.reduce
和Array.prototype.sort
是JavaScript中內置的高階函數。它們接受一個函數做爲參數,並應用這個函數到列表的每個元素。下面是一些內置高階函數的具體說明講解,以及和不使用高階函數狀況下的對比程序員
map()
(映射)方法最後生成一個新數組,不改變原始數組的值。其結果是該數組中的每一個元素都調用一個提供的函數後返回的結果。github
array.map(callback,[ thisObject]);
callback(回調函數)面試
[].map(function(currentValue, index, array) { // ... });
傳遞給 map
的回調函數(callback
)接受三個參數,分別是currentValue
——正在遍歷的元素、index(可選)——元素索引、array(可選)——原數組自己,除了 callback
以外還能夠接受 this 值(可選),用於執行 callback
函數時使用的this 值。算法
來個簡單的例子方便理解,如今有一個數組[1,2,3,4]
,咱們想要生成一個新數組,其每一個元素皆是以前數組的兩倍,那麼咱們有下面兩種使用高階和不使用高階函數的方式來實現。後端
// koala const arr1 = [1, 2, 3, 4]; const arr2 = []; for (let i = 0; i < arr1.length; i++) { arr2.push( arr1[i] * 2); } console.log( arr2 ); // [2, 4, 6, 8] console.log( arr1 ); // [1, 2, 3, 4]
// kaola const arr1 = [1, 2, 3, 4]; const arr2 = arr1.map(item => item * 2); console.log( arr2 ); // [2, 4, 6, 8] console.log( arr1 ); // [1, 2, 3, 4]
callback須要有return值,不然會出現全部項映射爲undefind;數組
// kaola const arr1 = [1, 2, 3, 4]; const arr2 = arr1.map(item => {}); console.log( arr2 ); // [ undefined, undefined, undefined, undefined ] console.log( arr1 ); // [1, 2, 3, 4]
//輸出結果 ["1", "2", "3"].map(parseInt);
看了這道題不知道會不會有大多數開發者認爲輸出結果是[1,2,3],錯誤
正確的輸出結果爲:
[1,NaN,NaN]
由於map
的callback
函數有三個參數,正在遍歷的元素, 元素索引(index), 原數組自己(array)。parseInt
有兩個參數,string和radix(進制),注意第二個參數進制當爲0或者沒有參數的時候,parseInt()會根據string來判斷數字的基數。當忽略參數 radix , JavaScript 默認數字的基數以下:
只傳入parseInt的話,map callback會自動忽略第三個參數array。而index參數不會被忽略。而不被忽略的index(0,1,2)就會被parseInt當作第二個參數。
將其拆開看:
parseInt("1",0);//上面說過第二個參數爲進制,因此"1",radix爲0上面提到過,會忽略,根據string 以 1 ~ 9 的數字開頭,parseInt() 將把它解析爲十進制的整數1。 parseInt("2",1);//此時將2轉爲1進制數,因爲超過進制數1,因此返回NaN。 parseInt("3",2);//此時將3轉爲2進制數,因爲超過進制數1,因此返回NaN。
因此最終的結果爲[1,NaN,NaN]
。
那麼若是想要獲得[1,2,3]
該怎麼寫。
["1","2","3"].map((x)=>{ return parseInt(x); });
也能夠簡寫爲:["1","2","3"].map(x=>parseInt(x));
這樣寫爲何就能返回想要的值呢?由於,傳一個完整函數進去,有形參,有返回值。這樣就不會形成由於參數傳入錯誤而形成結果錯誤了,最後返回一個經純函數處理過的新數組。
reduce()
方法對數組中的每一個元素執行一個提供的 reducer 函數(升序執行),將其結果彙總爲單個返回值。傳遞給 reduce 的回調函數(callback
)接受四個參數,分別是累加器 accumulator
、currentValue
——正在操做的元素、currentIndex
(可選)——元素索引,可是它的開始會有特殊說明、array(可選)——原始數組自己,除了 callback
以外還能夠接受初始值 initialValue
值(可選)。
例子,如今有一個數組 [0, 1, 2, 3, 4],須要計算數組元素的和,需求比較簡單,來看下代碼實現。
//koala const arr = [0, 1, 2, 3, 4]; let sum = 0; for (let i = 0; i < arr.length; i++) { sum += arr[i]; } console.log( sum ); // 10 console.log( arr ); // [0, 1, 2, 3, 4]
const arr = [0, 1, 2, 3, 4]; let sum = arr.reduce((accumulator, currentValue, currentIndex, array) => { return accumulator + currentValue; }); console.log( sum ); // 10 console.log( arr ); // [0, 1, 2, 3, 4]
上面是沒有 initialValue 的狀況,代碼的執行過程以下,callback 總共調用四次。
callback | accumulator | currentValue | currentIndex | array | return value |
---|---|---|---|---|---|
first call | 0 | 1 | 1 | [0, 1, 2, 3, 4] | 1 |
second call | 1 | 2 | 2 | [0, 1, 2, 3, 4] | 3 |
third call | 3 | 3 | 3 | [0, 1, 2, 3, 4] | 6 |
fourth call | 6 | 4 | 4 | [0, 1, 2, 3, 4] | 10 |
咱們再來看下有 initialValue 的狀況,假設 initialValue 值爲 10,咱們看下代碼。
//koala const arr = [0, 1, 2, 3, 4]; let sum = arr.reduce((accumulator, currentValue, currentIndex, array) => { return accumulator + currentValue; }, 10); console.log( sum ); // 20 console.log( arr ); // [0, 1, 2, 3, 4]
代碼的執行過程以下所示,callback 總共調用五次。
callback | accumulator | currentValue | currentIndex | array | return value |
---|---|---|---|---|---|
first call | 10 | 0 | 0 | [0, 1, 2, 3, 4] | 10 |
second call | 10 | 1 | 1 | [0, 1, 2, 3, 4] | 11 |
third call | 11 | 2 | 2 | [0, 1, 2, 3, 4] | 13 |
fourth call | 13 | 3 | 3 | [0, 1, 2, 3, 4] | 16 |
fifth call | 16 | 4 | 4 | [0, 1, 2, 3, 4] | 20 |
filter
(過濾,篩選) 方法建立一個新數組,原始數組不發生改變。
array.filter(callback,[ thisObject]);
其包含經過提供函數實現的測試的全部元素。接收的參數和 map 是同樣的,filter的callbac
k函數須要返回布爾值true或false. 若是爲true則表示經過啦!若是爲false則失敗,其返回值是一個新數組,由經過測試爲true的全部元素組成,若是沒有任何數組元素經過測試,則返回空數組。
來個例子介紹下,如今有一個數組 [1, 2, 1, 2, 3, 5, 4, 5, 3, 4, 4, 4, 4]
,咱們想要生成一個新數組,這個數組要求沒有重複的內容,即爲去重。
const arr1 = [1, 2, 1, 2, 3, 5, 4, 5, 3, 4, 4, 4, 4]; const arr2 = []; for (let i = 0; i < arr1.length; i++) { if (arr1.indexOf( arr1[i] ) === i) { arr2.push( arr1[i] ); } } console.log( arr2 ); // [1, 2, 3, 5, 4] console.log( arr1 ); // [1, 2, 1, 2, 3, 5, 4, 5, 3, 4, 4, 4, 4]
const arr1 = [1, 2, 1, 2, 3, 5, 4, 5, 3, 4, 4, 4, 4]; const arr2 = arr1.filter( (element, index, self) => { return self.indexOf( element ) === index; }); console.log( arr2 ); // [1, 2, 3, 5, 4] console.log( arr1 ); // [1, 2, 1, 2, 3, 5, 4, 5, 3, 4, 4, 4, 4]
callback
在過濾測試的時候,必定要是Boolean值嗎?
例子:
var arr = [0, 1, 2, 3]; var arrayFilter = arr.filter(function(item) { return item; }); console.log(arrayFilter); // [1, 2, 3]
經過例子能夠看出:過濾測試的返回值只要是弱等於== true/false就能夠了,而非非得返回 === true/false.
sort()
方法用原地算法對數組的元素進行排序,並返回數組,該排序方法會在原數組上直接進行排序,並不會生成一個排好序的新數組。排序算法如今是穩定的。默認排序順序是根據字符串Unicode碼點。
// 語法 arr.sort([compareFunction])
compareFunction
參數是可選的,用來指定按某種順序進行排列的函數。注意該函數有兩個參數:
參數1:firstEl
第一個用於比較的元素。
參數2:secondEl
第二個用於比較的元素。看下面的例子與說明:
// 未指明compareFunction函數 ['Google', 'Apple', 'Microsoft'].sort(); // ['Apple', 'Google', 'Microsoft']; // apple排在了最後: ['Google', 'apple', 'Microsoft'].sort(); // ['Google', 'Microsoft", 'apple'] // 沒法理解的結果: [10, 20, 1, 2].sort(); // [1, 10, 2, 20] //正確的結果 [6, 8, 1, 2].sort(); // [1, 2,6, 8] // 指明compareFunction函數 'use strict'; var arr = [10, 20, 1, 2]; arr.sort(function (x, y) { if (x < y) { return -1; } if (x > y) { return 1; } return 0; }); console.log(arr); // [1, 2, 10, 20]
若是沒有指明 compareFunction
,那麼元素會按照轉換爲的字符串的諸個字符的Unicode
位點進行排序。例如 "Banana" 會被排列到 "cherry" 以前。當數字按由小到大排序時,10 出如今 2 以前,但由於(沒有指明 compareFunction
),比較的數字會先被轉換爲字符串,因此在Unicode
順序上 "10" 要比 "2" 要靠前。
若是指明瞭 compareFunction
,那麼數組會按照調用該函數的返回值排序。即 a 和 b 是兩個將要被比較的元素:
compareFunction(a, b) 必須老是對相同的輸入返回相同的比較結果,不然排序的結果將是不肯定的。
看了上面sort
的排序介紹,我想小夥伴們確定會對sort排序算法的內部實現感興趣,我在sf上面搜了一下,發現有些爭議。因而去查看了V8引擎的源碼,發如今源碼中的710行
源碼地址: https://github.com/v8/v8/blob...
// In-place QuickSort algorithm. // For short (length <= 22) arrays, insertion sort is used for efficiency.
V8 引擎 sort 函數只給出了兩種排序 InsertionSort
和 QuickSort
,數量小於等於22的數組使用 InsertionSort
,比22大的數組則使用 QuickSort
,有興趣的能夠看看具體算法實現。
注意:不一樣的瀏覽器引擎可能算法實現並不一樣,我這裏只是查看了V8引擎的算法實現,有興趣的小夥伴能夠查看下其餘開源瀏覽器具體sort的算法實現。
對於要比較數字而非字符串,比較函數能夠簡單的以 a 減 b,以下的函數將會將數組升序排列,降序排序則使用b-a。
let compareNumbers= function (a, b) { return a - b; } let koala=[10, 20, 1, 2].sort(compareNumbers) console.log(koala); // [1 , 2 , 10 , 20]
返回一個函數,下面直接看兩個例子來加深理解。
咱們知道在判斷類型的時候能夠經過Object.prototype.toString.call
來獲取對應對象返回的字符串,好比:
let isString = obj => Object.prototype.toString.call( obj ) === '[object String]'; let isArray = obj => Object.prototype.toString.call( obj ) === '[object Array]'; let isNumber = obj => Object.prototype.toString.call( obj ) === '[object Number]';
能夠發現上面三行代碼有不少重複代碼,只須要把具體的類型抽離出來就能夠封裝成一個判斷類型的方法了,代碼以下。
let isType = type => obj => { return Object.prototype.toString.call( obj ) === '[object ' + type + ']'; } isType('String')('123'); // true isType('Array')([1, 2, 3]); // true isType('Number')(123); // true
這裏就是一個高階函數,由於 isType 函數將 obj => { ... }
這一函數做爲返回值輸出。
前言中的面試題,用 JS 實現一個無限累加的函數 add,示例以下:
add(1); // 1 add(1)(2); // 3 add(1)(2)(3); // 6
分析面試題的結構,都是將函數做爲返回值輸出,而後接收新的參數並進行計算。
咱們知道打印函數時會自動調用 toString()
方法(若是不知道的能夠去看個人這篇文章),函數 add(a) 返回一個sum(b)函數,函數 sum() 中累加計算 a = a + b,只須要重寫sum.toString()方法返回變量 a 就能夠了。
function add(a) { function sum(b) { // 使用閉包 a = a + b; // 累加 return sum; } sum.toString = function() { // 重寫toString()方法 return a; } return sum; // 返回一個函數 } add(1); // 1 add(1)(2); // 3 add(1)(2)(3); // 6
前面講了語言中內置的各類高階函數。知道了到底啊什麼是高階函數,有哪些類型的高階函數。那麼讓咱們本身建立一個高階函數吧!
假設 JavaScript 沒有原生的 map
方法。 咱們本身構建個相似map的高階函數,從而建立咱們本身的高階函數。
假設咱們有一個字符串數組,咱們但願把它轉換爲整數數組,其中每一個元素表明原始數組中字符串的長度。
const strArray=['JavaScript','PHP','JAVA','C','Python']; function mapForEach(arr,fn){ const newArray = []; for(let i = 0; i<arr.length;i++){ newArray.push({ fn(arr[i]) ); } return newArray; } const lenArray = mapForEach(strArray,function(item){ return item.length; }); console.log(lenArray);//[10,3,4,1,6]
咱們建立了一個高階函數 mapForEach ,它接受一個數組和一個回調函數 fn。 它循環遍歷傳入的數組,並在每次迭代時在 newArray.push 方法調用回調函數 fn 。
回調函數 fn 接收數組的當前元素並返回該元素的長度,該元素存儲在 newArray 中。 for 循環完成後,newArray 被返回並賦值給 lenArray。
咱們已經瞭解了高階函數和一些內置的高階函數,還學習瞭如何建立本身的高階函數。簡而言之,高階函數是一個能夠接收函數做爲參數,甚至返回一個函數的函數。 它就像常規函數同樣,只是多了接收和返回其餘函數的附加能力,即參數和輸出。
你們好,我是koala,公衆號「程序員成長指北」做者,這篇文章是【JS必知必會系列】的高階函數講解。目前在作一個node後端高級工程師進階路線,加入咱們一塊兒學習吧!