JavaScript進階之高階函式篇
JavaScript進階之高階函式篇
簡介:歡迎大家來到woo爺說前端;今天給你們帶來的是JavaScript進階的知識,接下來的系列都是圍繞著JavaScript進階進行闡述;首先我們第一篇講的是高階函式。
高階函式定義:高階函式是指操作函式的函式;一般情況在專案開發過程中都會分兩種情況
- 函式可以作為引數傳遞到另外一個函式執行
- 函式可以作為返回值輸出被執行
讓我們來用一張圖描述一下高階函式
以上是高階函式的要求。我們在開發專案使用到的JavaScript的函式明顯滿足高階函式的要求;因此我們再寫程式碼過程中可以利用高階函式對基本函式已經業務邏輯進行再次封裝,或者作為回撥函式。接下來我們開始見識一下平時業務中怎麼使用高階函式;如何去封裝高階函式?、
第一種模式:作為引數傳遞
在業務程式碼中經常遇到兩個基本函式邏輯相同但業務邏輯不用的情況下;我們可以把這兩個相同的邏輯封裝成一個高階函式,從而把不同的邏輯寫在函式裡面作為引數傳遞到封裝好的函式裡面。這樣可以實現業務邏輯一些變化一些不變的場景,這種是我們最常見的場景;簡稱為回撥函式。
接下來讓我們來舉例子說明一下
例子1:
1 //兩個不同的函式,但是其中一部分邏輯是相同的一部分是可變的 2 function a(){ 3 console.log("我是一個函式"); 4 console.log("我是a函式"); 5 } 6 7 8 function b(){ 9 console.log("我是一個函式"); 10 console.log("我是b函式") 11 } 12 13 /* 14 以上就是我們兩個基本得函式,我們分別執行這兩個函式 15 */ 16 17 a(); 18 b()
這就是我們上面執行的結果,可以發現兩個函式存在著相同點。那麼我們接下來對兩個函式進行下一步的處理
function c(fn){ console.log("我是一個函式") fn() } //把相同的邏輯封裝成一個c函式,不同邏輯的作為fn引數函式傳遞進去執行 c(function(){ console.log("我是a函式") }) c(function(){ console.log("我是b函式") }) //由此可見我們實現我們想要的呈現方式,接下來我們看一下執行的結果是怎麼樣的
這是我們最後執行的結果,跟上面的執行結果是一樣的,可見高階函式可以讓程式碼更加多變性,更加簡潔明瞭、易懂;這是我們平時常見的場景。
例子2:
其實我們還有一種場景更加經常在專案裡面遇到。我們經常會在專案中使用ajax請求或者使用axios請求等等一些非同步請求;一般往往我們不關心請求過程(請求過程是相同的)、只想要請求的結果處理不同業務邏輯。我們可以利用高階函式對請求統一封裝,也叫請求攔截
var httpsAjax = function(obj,callback){ var {url,data,type} = obj; $.ajax({ url:url, type:type || 'POST' , data:dara || {}, success:function(res){ //利用typeof 判斷資料型別,如果是傳進來的是函式,我們就執行回撥函式 if(typeof callback === 'function'){ callback(res) } } }) } httpsAjax({ url:"xxx/get/user", type:"GET", data:{} },function(res){ //操作一定的業務邏輯 console.log(res) })
第一種模式總結:以上就是我們最常見的基本高階函式的使用,一般我們會用函式作為引數傳遞到另外一個引數裡面,然後另外一個引數執行傳遞進去的函式,從而形成了回撥函式(高階函式)
第二種模式:作為返回值輸出
相比把函式當作引數傳遞,函式當作返回值輸出的應用場景也有很多。讓函式繼續返回一個可執行的函式,這樣意味著運算過程是可延續的,就比如我們經常用到陣列排序Array.sort()方法
下面是使用Object.prototype.toString方法判斷資料型別一系列的isType函式例子:
var isString = function(obj){ return object.prototype.toString.call(obj) === '[object String]' } var isArray = function(obj){ return object.prototype.toString.call(obj) === '[object Array]' } var isNumber = function(obj){ rturn object.prototype.toString.call(obj) === '[object Number]' } isString("我是一個數組串");//true isArray(["1","2"]);//true isNumber(1)//true
注意:其實我們會發現上面的三個方法有大部分相同的邏輯object.prototype.toString.call(obj),不同的是處理邏輯返回的字串結果,為了避免冗餘的程式碼,我們將其封裝成一個函式is_type();
var is_type = function(obj,type){ return object.prototype.toString.call(obj) == '[object ' + type + ']' } console.log(is_type(11,'Number'));//true console.log(is_type(['a','b'],"Array");//true
注意:上面就是我們進行封裝的方法,可以發現我們提取出來之後,程式碼量少了很多,更加明確,但是我們會發現我們需要傳遞兩個引數,而且兩個引數的含義要一一對上,不然我們在業務程式碼上一旦寫錯沒對應上,那我們寫的邏輯就會出現bug。所以我們將這個方法再次封裝一下,把type先區分型別作為引數傳遞進去,利用高階函式擁有儲存變數的作用,返回一個函式,分析我們傳遞的obj時到底是什麼型別。
var isType = function(type){ //先傳遞一個type引數作為資料型別,利用高階函式擁有儲存變數的特性,返回一個可持續執行的函式 return function(obj){ return object.prototype.toStirng.call(obj) === '[object '+ type +']' } } //先細分到每種型別,這樣我們就可以明確知道具體型別呼叫什麼方法 var isString = isType("String"); var isArray = isType("Array"); var isNumber = isType("Number");
console.log(isArray([1,2,3]))//true
第二種模式總結:以上就是我們第二種模式的例子。顯然而見我們可以利用這種模式讓一個函式返回另外一個函式,讓函式的運算繼續延續下去,這就是第二種模式作為返回值輸出
以上就是高階函式兩種模式的基本理解與演示。相信對你在平時開發專案中有很多幫助;同時也要注意平時開發專案不是每個方法都要使用高階函式,高階函式使用場景就是以上兩種。不然會導致大材小用。接下來我們來講一下JavaScript經常用到的高階函式map()/reduce()、filter()、sort()四個方法。
- map()方法
- 定義:map()方法遍歷原有陣列返回一個新陣列,陣列中的元素為原始陣列元素呼叫函式處理後的值,按照原始陣列元素順序依次處理元素。
- 注意:不會對空陣列進行檢測、返回的是新陣列不會改變原始的陣列
- 語法格式 :
newArray.map(function(item){ //遍歷newArray item是newArray元素;類似於newArray[i]
return item
//必須有return,反正不會產生新的陣列
})
map()方法定義在JavaScript得Array中,我們呼叫Array得map方法,傳入我麼自己想要得函式,就等到一個新得array作為結果:
例子:
function pow(x){ return x*x } var arr = [1,2,3,4,5]; console.log(arr.map(pow));//[1,4,9,16,25]
這就是一個簡單得map方式呼叫,傳遞一個pow自定義函式,對原陣列每個元素進行乘冪,得到一個新的陣列
再比如我們平時經常會遇到這種,需要取陣列物件中某個屬性用來做一組陣列
例子:
var arr = [ { name:"張三", type:2 }, { name:"李四", type:3 } ] //要求拿到type屬性組成新的陣列 //以往的寫法for迴圈 var new_arr = [] for(var i = 0 ; i < arr.length ; i++){ var obj = arr[i] for(var key in obj){ if(key == 'type'){ new_arr.push(obj[key]) } } } //map的寫法 var new_arr = arr.map(function(item){ return item.type })
從上面可以看到一個寫法是for迴圈的寫法,一個是map的寫法;雖然兩個得到的結果都是一樣的[2,3];但是從程式碼量來說,for迴圈過於冗餘,map簡單方便。所以以後遇到這種場景可以使用map方法更加方便
2.reduce()方法
1、定義:接收一個函式作為累加器,陣列中的每一個值(從左到右)開始遍歷,最終計算為一個值
2、注意:對空陣列是不會執行回撥函式的
3、語法格式 : new Array.reduce(callback,initialValue)
對語法格式的理解:
reduce(callback,initialValue)會傳入兩個變數,第一個引數是回撥函式(callback)和第二個初始值(initialValue)。
第一個引數回撥函式(callback)有四個傳入引數,prev和next,index和array。prev和next是必傳的引數。
第一個引數初始值(initialValue)決定回撥函式的第一個引數prev的取值結果,當reduce傳入initialValue時,prev的預設值就是initialValue,當reduce沒有傳入initialValue時,那麼prev的預設值就是原數值的第一個元素值。
下面解釋一下
var arr = ["apple","orange"]; //第一種沒有傳遞initialValue的情況 function getValue(){ return arr.reduce(function(prev,next){ console.log("prev",prev); console.log("next",next) return prev; }) } console.log("getValue",getValue())
執行結果可以看出來我們沒有傳遞initialValue的情況,prev取得是arr得第一個元素值開始遍歷
接下來我們看一下傳遞initialValue得情況
var arr = ["a","b"]; //傳遞initialValue情況時 function getValue(){ return arr.reduce(function(prev,next){ console.log("prev",prev); console.log("next",next); prev[next] =1; return prev; },{}) //initialValue傳遞一個空物件 } console.log("getValue",getValue());
可以看到我們執行得結果,當傳遞initialValue得時候,prev得預設值就是你傳遞的initialValue;而我們就可以利用傳遞的預設值進行一系列業務邏輯處理。達到我們想要的效果
接下來我們來看一下經常業務中是怎麼使用reduce()的。
案例1:計算陣列總和
var numArray = [1,2,3,4,5]; //用for迴圈來計算 var num = 0; for(var i = 0 ; i < numArray.length ; i++){ num = num + numArray[i] } console.log(num);//15 //利用reduce方法 var res = numArray.reduce(function(prev,next){ return prev + next },0) console.log(res) ;//15
利用for迴圈我們要先宣告一個全域性變數作為預設值。我們知道開發專案過程中,儘量不要使用全域性變數,而使用reduce我們可以完全避過,而且更加直接。這種是簡單的使用reduce()。
案例2:合併二維陣列
var arr = [[0,1],[2,3],[4,5]]; var res = arr.reduce(function(prev,next){ return prev.concat(next) },[]) console.log(res);//[0,1,2,3,4,5];
我們可以傳遞一個空陣列作為預設值,對原始的陣列元素進行合併成一個新的陣列。
案例3:統計一個數組中的單詞重複有幾個
var arr = ["apple","orange","apple","orange","pear","orange"]; //不用reduce時 function getWorad(){ var obj = {}; for(var i = 0 ; i < arr.length ; i++){ var item = arr[i]; if(obj[item]){ obj[item] = obj[item] + 1 }else{ obj[item] = 1 } } return obj } console.log(getWorad()); function getWorad2(){ return arr.reduce(function(prev,next){ prev[next] = (prev[next] + 1 ) || 1; return prev; },{}) } console.log(getWorad2())
最後兩個的結果都是一樣的,其實我們使用reduce()方法會讓我們的邏輯變得簡單易處理。從而拋棄我們冗餘的程式碼,讓程式碼看起來更加明瞭。相信你們再平時的業務中也會遇到這種業務邏輯的。
3、filter()方法
1、定義:filter()也是一個常用的操作,它用於把Array的某些元素過濾調,然後返回剩下的元素,和map方法類似,Array的filter也接收一個函式,和map()不同的是;filter()把傳入的函式依次作用於每個元素,然後根據返回值是true還是false決定保留還是丟失該元素
案列1:例如,在一個Array中,刪掉偶數,只保留奇數,可以這麼寫:
var arr = [1,2,3,4,5]; var r = arr.filter(function(item){ return item%2 !== 0 ; }) console.log(r)
可以看到,我們利用filter對元素的過濾,只要元素取餘不等於零,就返回來,等於零就拋棄,最後組成一個新的陣列。
案例2:把一個arr中的空字串去掉,可以這麼寫:
var arr = ['a','b','','c'] var r = arr.filter(function(item){ return item && item.trim(); }) console.log(r)
我們可以利用filter過濾陣列中空的字串,返回一個沒有空字串的陣列。方便簡單,接下來我們來解析一下filter方法回撥函式所帶的引數有哪些
//先看一下filter的語法格式 var r = Array.filter(function(ele,index,_this){ console.log(ele); console.log(index); console.log(_this); return ture; }) /* 可見用filter()這個高階函式,關鍵在於正確實現一個篩選函式 回撥函式:filter()接收的回撥函式,其實可以有多個引數。通常我們僅使用第一個引數,表示Array的某個元素,回撥函式還可以接收另外兩個引數,表示元素的位置,和陣列本身。 */
業務中最經常用到的還是用filter來去除陣列中重複的元素
var r,arr = [1,1,3,4,5,3,4]; r= arr.filter(function(ele,index,self){ return self.indexOf(ele) === index; }) console.log(r)
這樣我們就很快速等到一個沒有重複元素的陣列。這也是我們經常遇到的去重,也是一種經常面試問到的。
4、sort()方法
1、定義:sort() 方法用於對陣列的元素進行排序,並返回陣列。
2、語法格式:arrayObject.sort(sortby);
3、注意:引數sortby 可選,用來規定排序的順序,但必須是函式。
//接下來我們就來看一下到底如何排序 //從小到大的排序,輸出:[1,2,3,9,56,87] var arr = [9,87,56,1,3,2]; arr.sort(function(x,y){ if(x < y){ return -1 } if(x > y){ return 1 } return 0; }) //如果我們想要倒序排序;我們可以把大的放在前面[5,4,3,2,1] var arr = [1,2,3,4,5]; arr.sort(function(x , y){ if(x < y ){ return 1 } if(x > y){ return -1 } return 0 }) //在比如我們想要對字串排序也可以實現,以字串的首個字元,按照ASCII的大小 //進行比較的,忽略大小寫字母 ['good','apple','moide'] var arr = ['good','apple','moide']; arr.sort(function(s1,s2){ var x1 = s1.toUpperCase();//轉成大寫 var x2 = s2.toUpperCase(); if(x1 < x2){ return -1 } if(x1 > x2){ return 1 } return 0 }) //忽略大小寫其實是把值轉成大寫或者全部小寫,再去做比較 //注意:sort()方法會直接對原有的Array陣列進行修改,他返回的結果仍是當前的Array //還有往往我們會在程式碼裡見到很多陣列物件,相對陣列物件的某個屬性做排序那要怎麼實現呢 //下面就是對陣列物件的排序例子 var arr = [ { Name:'zopp', Age:10 }, { Name:'god', Age:1, }, { Name:'ytt', Age:18 } ]; //簡單的寫法;預設的升序 function compare(prototype){ return function(a,b){ var value1 = a[prototype]; Var value2 = b[prototype]; return value1 - value2 } } //一般我們往往會把引數函式單獨寫一個函式;達到清晰明確,多變性 console.log(arr.sort(compare(“Age”)));
/*最後我們寫了這麼多例子,也發現作為可變的部分引數函式其實無非就是兩種;一種是升序一種是降序。其實我們還可以把這可變的引數函式封裝成一個方法,利用傳參的方式來說明我們是要升序還是降序 */ /**陣列根據陣列物件中的某個屬性值進行排序的方法 * 使用例子:newArray.sort(sortBy(type,rev,[,key])) * @param type 代表newArray的型別,’number’表示數值陣列[1,2],’string’表示字串陣列[‘abhs’,’as’],’obj’表示物件陣列[{},{}],如果是obj的話第三個引數必填 * @param rev true表示升序排列,false降序排序 * @param key 非必填的第三個函式,是作為如果type是obj的話作為屬性傳遞 * */ var sortBy = function(type,rev,key){ //第二個引數不傳預設就是升序排列 if(!rev){ rev = 1 }else{ rev = rev ? 1 : -1; } return function(a,b){ //如果是string的話我們就要處理一下統一大寫還是小寫 if(type == 'string'){ a = a.toUpperCase(); b = b.toUpperCase(); } //如果是obj的話我們就是取對應的屬性值 if(type == 'obj'){ a = a[key]; b = b[key]; } if(a < b){ return rev * -1; } if(a > b){ return rev * 1; } return 0; } } //這就是我們最後想要的統一封裝,大家也可以拿著自己去修改一下,這種封裝是最後返回一個處理好的匿名函式,也是高階函式中的一種,後面我們也會提到
總結:
- 如果要得到自己想要的結果,不管是升序還是降序,就需要提供比較函數了。該函式比較兩個值的大小,然後返回一個用於說明這兩個值的相對順序的數字。
- 比較函式應該具有兩個引數 a 和 b,其返回值如下:
- 若 a 小於 b,即 a - b 小於零,則返回一個小於零的值,陣列將按照升序排列。
- 若 a 等於 b,則返回 0。
- 若 a 大於 b, 即 a - b 大於零,則返回一個大於零的值,陣列將按照降序排列。