對於ES6的語法的引入,很多時候是我所不理解的,甚至是知道用法(api),卻不清楚何使用、為什麼用。一次偶然的機會,在Youtube看到了一個系列教程,覺得非常不錯,遂記筆記,進行詳細的梳理和整合,非常推薦大家去看原版啦,老師是個10年的coder,講課比小品還生動的(@fun fun function)
因為一次可能更新不完,之後有新的內容會更新在github,歡迎小夥伴star,共同進步.傳送門
filter
從一段程式開始
var tripe = function(x) {
return x*3;
}
var waffle = tripe;
waffle(3);
複製程式碼
這是一段簡單到不能再簡單的程式碼了,但是,這也是JS的特殊之處。一切皆物件,函式也是物件,函式可以是一個變數(var)。
正式這樣的特殊性,讓函式可以作為另一個函式的引數(callback)進行傳遞,這種方式就是高階函式--函式中包含著另一個函式。下面就從filter
函式舉例來說
現在有一個需求,要找到一個陣列中比3大的數字,並拿出來。
先看傳統的,不用filter的方案
function big(arr) {
let ret = [];
for(let i = 0; i<arr.length; i++) {
if arr[i] > 3
ret.push(arr[i]);
}
return ret;
}
// 如果現在需求改成找比3小的?再寫程式碼?而且沒有複用性。
複製程式碼
好了,有沒有可能簡化或者優化下程式碼呢?讓它更具有可維護性,更清楚點? 來看看最簡單的函數語言程式設計,使用filter的例子。
var arr = [1,2,3,4];
var b = arr.filter((x)=>{return x >3 });
console.log(b) // [4]
複製程式碼
好了,可以看到filter接收一個函式作為引數,函式會遍歷陣列每一個item,當return
為true
時,會返回當前的item
。
這只是一個簡單的邏輯,我們說的函數語言程式設計,目的是為了讓函式儘量功能簡單、邏輯清晰,沒有耦合的組織可以讓函式複用和程式碼高效上更有利。所以我們改寫下上面的內容
var isBig = function(x) {
return x > 3;
}
var b = arr.filter(isBig);
複製程式碼
這樣子,就完成了解耦,我們就是把函式當作一個變數、引數傳給了另一個函式,這就是最基本的思想。 這樣做的好處是什麼?比如我們現在需要找到比3小的數字,只需要一行程式碼
var c = arr.reject(isBig);
複製程式碼
是的,就是這麼簡單,是不是很神奇? 想想傳統的思路,找比3大和比3小是怎麼做的?
map
map 是對映的意思,其實和filter很相似,只不過map是對映和變換原陣列。什麼意思?看下面的需求
一個陣列中包含了很多物件,我想要獲取這些物件的名字
var animals = [
{name:'cindy', species:'dog'},
{name:'hash', species:'duck'},
{name:'gigi', species:'rabit'},
{name:'chik', species:'cat'},
]
複製程式碼
好了,傳統的做法又是要迴圈、遍歷然後push之後,返回了。用map就很簡單
var name = animals.map(obj=>{
return obj.name;
})
複製程式碼
注意,這就是map和filter的不同: filter遍歷,並根據true
和flase
決定是否返回原物件(item); map就是純粹的變換(transform)了。
當然,我們可以利用map,讓內容變的更加豐富
var name = animals.map(obj=>{
return `${obj.name}是一個${obj.species}`
})
複製程式碼
reduce
reduce最基本的用法如下:
var arr = [1,2,3,4,5];
var sum = arr.reduce((sum,next)=>{
return sum + next;
},0)
複製程式碼
reduce接收兩個引數,第一個是callback
,第二個引數是初始值
。arr.reduce(callback(p1,p2),init)
為什麼要初始值呢?注意一下回撥函式中,是兩個引數,p1引數是上一次迴圈(loop)的返回結果,p2引數是當前遍歷到的(item)。 很顯然,那麼第一次遍歷時的p1,就是由init
提供的。
reduce絕不僅僅是遍歷一個陣列或者做一個加法這麼簡單。他可以做更復雜事情,可以對物件進行操作。 比如我們看到這個例子:
var name = [
'sam\tblender\t200\t1 ',
'sam\tpot\t130\t5 ',
'nacy\tconaver\t20\t3 ',
'amy\tpot\t130\t2 ',
'amy\tblender\t4\t2 '
]
複製程式碼
現在需要統計這個陣列中,sam\nacy\amy的個人財產和資料。 怎麼辦呢? 利用上面介紹的方法組合,可以很好的做到
思路:
- map對每個字串進行處理和變換
- 使用reduce進行統計
var output =
name.map((item)=>{item.trim().split('\t')})
.reduce((custormers,line)=>{
custormers[line[0]] = custormers[line[0]] || [];
custormers.push({
name: line[0],
property: line[1],
price:line[2]
quilty: line[3]
})
},{})
複製程式碼
嗯,可能有點複雜了,一行一行來看。
map的作用: 經過map,將原來每一項的字串,轉換成了陣列,現在是
這樣的形式:
[
[[sam],[blender],[200],[1]],
[[sam],[pot],[130],[5]],
...
]
好了,繼續就到了reduce。我們給reduce穿的`init`是一個空物件,也就是
會建立一個物件作為返回值
第一次迴圈:
custormers:{} line: [[sam],[blender],[200],[1]]
custormers.sam = [];
custormers.sam.push({...})
==> 最終
custormers = {
sam:[{
name:sam,
property:blender,
price:200,
quilty:1
}]
}
第二次迴圈:
custormers:{sam:[...]} line:[[sam],[pot],[130],[5]]
custormers.sam = [...]
繼續為sam這個物件,增加“財產"
...
...
複製程式碼
所以看到了,這就是reduce的威力。
reduce 實現compose
好了,reduce的作用可能遠不止於前面介紹的那些,因為至少我們在菜鳥教程上看的話,會很醒目的告訴你,reduce可以實現函式compose
我擦,這是什麼東西?趕快了解了一下
什麼是compose
compose就是執行一系列的任務(函式),比如有以下任務佇列
let tasks = [step1, step2, step3, step4]
複製程式碼
每一個step都是一個步驟,按照步驟一步一步的執行到結尾,這就是一個compose compose在函數語言程式設計中是一個很重要的工具函式,在這裡實現的compose有三點說明
- 第一個函式是多元的(接受多個引數),後面的函式都是單元的(接受一個引數)
- 執行順序的自右向左的
- 所有函式的執行都是同步的
程式導向的實現
使用遞迴的思想,不斷的檢測任務佇列中是否有任務,如果有任務就執行,並把執行結果傳遞. 實現過程如下
var compose = function(args) {
let length = args.length;
let count = lenth - 1;
let result;
return function inner(...inArgs) {
result = args[count].apply(null,inArgs);
if(coun <= 0) {
return result;
}
count--;
return args[count].apply(null,result);
}
}
複製程式碼
generator實現
generator是會專門寫一個介紹的(就在近期更新),同樣是看了很多內容後,理解了它才做的。 因為generator本身就是實現中斷、處理、再中斷的流程。 generator遇到yield就會丟擲一個iterator,而其next()方法可以傳遞引數,就可以實現傳值,將上一步的執行結果,作為下一步的引數。 好了,下面就generator來實現。
function * iterateSteps(steps) {
let param;
steps.forEach((step)=> {
if(param) {
param = yield step.apply(null,param)
} else {
param = yield;
}
})
}
const compose = function(steps) {
let g = iterateSteps(steps); //g是一個generator,通過next()執行
return function(...args) {
let val = steps.pop().apply(null,args) //val返回的值作為將作為第二個函式的引數
//第一個函式無法傳參,因此空消耗一個yield
g.next()
return steps.reverse.reduce((result,fn)=>{
g.next(result).value //返回
},val)
}
}
複製程式碼
解釋:
這個可能看起來就有點讓人迷糊了。 首先,generator就是一步步來執行函式的,只不過,與普通函式不同,執行到函式的地方,它會把"執行緒"丟擲來,讓外面來進行解決。當下次迭代到next(val)
的時候,會把val值傳回。
首先,因為第一次的執行無法傳引數,所以我們將param定義,但是不賦值。 在if-else
中,第一次將走到else,執行param = yield
。 所以,當第一次執行next的時候,實際只是為了消耗yield。
在compose函式中,首先執行iterataSteps,接下來使用reduce來自動執行
這個generator,就可以使實現compose了。注意這裡Steps.pop.apply(null,args)
是彈出了最後一個函式,並執行,拿到的值,作為reduce
的init
引數。
總結
reduce實現compose的意思,通過上面的函式,已經解釋到了。其實,compose的思路還有Promise、函式交織等思路,這裡不做詳細介紹了,可以在這裡參考