摘要:函數作為參數情況,,和是中內置的高階函數。知道了到底啊什么是高階函數,有哪些類型的高階函數。公眾號技術棧路線大家好,我是,公眾號程序員成長指北作者,這篇文章是必知必會系列的高階函數講解。
前言
一道經典面試題:
//JS實現一個無限累加的add函數 add(1) //1 add(1)(2) //3 add(1)(2)(3) //6
當大家看到這個面試題的時候,能否在第一時間想到使用高階函數實現?想到在實際項目開發過程中,用到哪些高級函數?有沒有想過自己創造一個高階函數呢?開始本篇文章的學習
文章已同步都github博客地址:
程序員成長指北技術棧博客地址
高階函數英文叫 Higher-order function。高階函數是對其他函數進行操作的函數,操作可以是將它們作為參數,或者返回它們。簡單總結為高階函數是一個接收函數作為參數或者將函數作為返回輸出的函數。
函數作為參數情況Array.prototype.map,Array.prototype.filter,Array.prototype.reduce和Array.prototype.sort是JavaScript中內置的高階函數。它們接受一個函數作為參數,并應用這個函數到列表的每一個元素。下面是一些內置高階函數的具體說明講解,以及和不使用高階函數情況下的對比
Array.prototype.mapmap()(映射)方法最后生成一個新數組,不改變原始數組的值。其結果是該數組中的每個元素都調用一個提供的函數后返回的結果。
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]map高階函數注意點
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]map高階函數對應的一道經典面試題
//輸出結果 ["1", "2", "3"].map(parseInt);
看了這道題不知道會不會有大多數開發者認為輸出結果是[1,2,3],錯誤
正確的輸出結果為:
[1,NaN,NaN]
因為map的callback函數有三個參數,正在遍歷的元素, 元素索引(index), 原數組本身(array)。parseInt有兩個參數,string和radix(進制),注意第二個參數進制當為0或者沒有參數的時候,parseInt()會根據string來判斷數字的基數。當忽略參數 radix , JavaScript 默認數字的基數如下:
如果 string 以 "0x" 開頭,parseInt() 會把 string 的其余部分解析為十六進制的整數。
如果 string 以 0 開頭,那么 ECMAScript v3 允許 parseInt() 的一個實現把其后的字符解析為八進制或十六進制的數字。
如果 string 以 1 ~ 9 的數字開頭,parseInt() 將把它解析為十進制的整數。
只傳入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));
這樣寫為什么就能返回想要的值呢?因為,傳一個完整函數進去,有形參,有返回值。這樣就不會造成因為參數傳入錯誤而造成結果錯誤了,最后返回一個經純函數處理過的新數組。
Array.prototype.reducereduce() 方法對數組中的每個元素執行一個提供的 reducer 函數(升序執行),將其結果匯總為單個返回值。傳遞給 reduce 的回調函數(callback)接受四個參數,分別是累加器 accumulator、currentValue——正在操作的元素、currentIndex(可選)——元素索引,但是它的開始會有特殊說明、array(可選)——原始數組本身,除了 callback 之外還可以接受初始值 initialValue 值(可選)。
如果沒有提供 initialValue,那么第一次調用 callback 函數時,accumulator 使用原數組中的第一個元素,currentValue 即是數組中的第二個元素。 在沒有初始值的空數組上調用 reduce 將報錯。
如果提供了 initialValue,那么將作為第一次調用 callback 函數時的第一個參數的值,即 accumulator,currentValue 使用原數組中的第一個元素。
例子,現在有一個數組 [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]使用高階函數 無 initialValue 值
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的callback函數需要返回布爾值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]filter注意點說明
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.
Array.prototype.sortsort() 方法用原地算法對數組的元素進行排序,并返回數組,該排序方法會在原數組上直接進行排序,并不會生成一個排好序的新數組。排序算法現在是穩定的。默認排序順序是根據字符串Unicode碼點。
// 語法 arr.sort([compareFunction])
compareFunction參數是可選的,用來指定按某種順序進行排列的函數。注意該函數有兩個參數:
參數1:firstEl
第一個用于比較的元素。
參數2:secondEl
第二個用于比較的元素??聪旅娴睦优c說明:
// 未指明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) 小于 0 ,那么 a 會被排列到 b 之前;
如果 compareFunction(a, b) 等于 0 , a 和 b 的相對位置不變。備注: ECMAScript 標準并不保證這一行為,而且也不是所有瀏覽器都會遵守(例如 Mozilla 在 2003 年之前的版本);
如果 compareFunction(a, b) 大于 0 , b 會被排列到 a 之前。
compareFunction(a, b) 必須總是對相同的輸入返回相同的比較結果,否則排序的結果將是不確定的。
sort排序算法的底層實現看了上面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]函數作為返回值輸出
返回一個函數,下面直接看兩個例子來加深理解。
isType 函數我們知道在判斷類型的時候可以通過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 => { ... } 這一函數作為返回值輸出。
add求和函數前言中的面試題,用 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代碼分析講解: 我們創建了一個高階函數 mapForEach ,它接受一個數組和一個回調函數 fn。 它循環遍歷傳入的數組,并在每次迭代時在 newArray.push 方法調用回調函數 fn 。
回調函數 fn 接收數組的當前元素并返回該元素的長度,該元素存儲在 newArray 中。 for 循環完成后,newArray 被返回并賦值給 lenArray。
總結我們已經了解了高階函數和一些內置的高階函數,還學習了如何創建自己的高階函數。簡而言之,高階函數是一個可以接收函數作為參數,甚至返回一個函數的函數。 它就像常規函數一樣,只是多了接收和返回其他函數的附加能力,即參數和輸出。
公眾號技術棧路線大家好,我是koala,公眾號「程序員成長指北」作者,這篇文章是【JS必知必會系列】的高階函數講解。目前在做一個node后端高級工程師進階路線,加入我們一起學習吧!
加入我們
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/105757.html
摘要:哪吒別人的看法都是狗屁,你是誰只有你自己說了才算,這是爹教我的道理。哪吒去他個鳥命我命由我,不由天是魔是仙,我自己決定哪吒白白搭上一條人命,你傻不傻敖丙不傻誰和你做朋友太乙真人人是否能夠改變命運,我不曉得。我只曉得,不認命是哪吒的命。 showImg(https://segmentfault.com/img/bVbwiGL?w=900&h=378); 出處 查看github最新的Vue...
摘要:通過請求和響應的交換達成通信協議中已經規定了請求是從客戶端發出,最后由服務端響應這個請求并返回。隨后的字符串指明了請求訪問的資源對象。協議自身不對請求和響應之間的通信狀態進行保存,也就是說這個級別。從前發送請求后需等待并受到響應。 showImg(https://segmentfault.com/img/bVbmDsG?w=1024&h=538); http協議用戶客戶端和服務器之間的...
摘要:歡迎來我的個人站點性能優化其他優化瀏覽器關鍵渲染路徑開啟性能優化之旅高性能滾動及頁面渲染優化理論寫法對壓縮率的影響唯快不破應用的個優化步驟進階鵝廠大神用直出實現網頁瞬開緩存網頁性能管理詳解寫給后端程序員的緩存原理介紹年底補課緩存機制優化動 歡迎來我的個人站點 性能優化 其他 優化瀏覽器關鍵渲染路徑 - 開啟性能優化之旅 高性能滾動 scroll 及頁面渲染優化 理論 | HTML寫法...
閱讀 2074·2021-11-15 17:57
閱讀 739·2021-11-11 16:54
閱讀 2589·2021-09-27 13:58
閱讀 4067·2021-09-06 15:00
閱讀 950·2021-09-04 16:45
閱讀 3505·2019-08-30 15:56
閱讀 1784·2019-08-30 15:53
閱讀 1605·2019-08-30 14:12