1. 程式人生 > >ES6中的高階函式:如同 a => b => c 一樣簡單

ES6中的高階函式:如同 a => b => c 一樣簡單

ES6來啦!隨著越來越多的程式碼庫和思潮引領者開始在他們的程式碼中使用ES6,以往被認為是“僅需瞭解”的ES6特性變成了必需的程式碼常識。這不僅僅是新的語法學習 - 在許多範例中, ES6中新的語言特性可以讓在ES5中寫起來非常麻煩的表達變得更加簡單,進而鼓勵了新表達方式的使用。下面我們將關注一個這樣簡潔表達的使用範例:ES6中的箭頭函式如何使高階函式的書寫更加簡便。

高階函式是至少具有以下兩種功能之一的函式:

  1. 使用一個或多個函式作為實參
  2. 返回一個函式作為結果

本文的目的並不是說服你立即採用這種新方式,儘管筆者非常鼓勵你嘗試使用!本文旨在讓你熟悉這種表達方式,這樣當你在遇到其他人基於ES6寫的程式碼庫時,不會如筆者當初一樣看著這些陌生程式碼撓頭不解。如果你在此之前需要先複習一下箭頭語法的知識,請先查閱

這篇文章

希望你熟悉有返回值的箭頭函式:

const square = x => x * x;

  
  • 1

但是下面這兩行程式碼是什麼意思?

const has = p => o => o.hasOwnProperty(p);
const sortBy = p => (a, b) => a[p] > b[p];

  
  • 1
  • 2

“p 返回 o 返回 o.hasOwnProperty…”這句程式碼是什麼意思?我們能這樣用嗎?

理解語法

為了說明帶箭頭的高階函式的書寫規則,讓我們一起來看一個經典示例:加法函式。在ES5中會這樣寫:

function add(x){
  return function(y){
     return y + x;
  };
}
var addTwo = add(2);
addTwo(3);          // => 5
add(10)(11);        // => 21 

  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

我們的加法函式輸入x,返回了一個輸入y返回值是y + x的函式。那我們應該如何用箭頭函式表達這個函式呢?已知…

  1. 箭頭函式定義是一個表示式,並且
  2. 箭頭函式隱式返回單一表達式結果

那麼我們所要做的就是讓另一個箭頭函式作為我們箭頭函式的函式體,因此表達方式如下:

const add = x => y => y + x;
// outer function: x => [inner function, uses x]
// inner function: y => y + x;

  
  • 1
  • 2
  • 3

現在我們就可以通過一個與變數x相關的返回值建立內部函式:

const add2 = add(2);// returns [inner function] where x = 2
add2(4);            // returns 6: exec inner with y = 4, x = 2
add(8)(7);          // 15 

  
  • 1
  • 2
  • 3

我們所寫的加法函式並不是超級有用,但是它可以說明一個外函式如何輸入一個以x為變數的引數函式,並在它返回的函式中引用此函式。

使用者分類

假如你在github上看到了一個ES6程式碼庫,並且遇到了下面這樣的程式碼:

const has = p => o => o.hasOwnProperty(p);
const sortBy = p => (a, b) => a[p] > b[p];

let result;
let users = [
  { name: 'Qian', age: 27, pets : ['Bao'], title : 'Consultant' },
  { name: 'Zeynep', age: 19, pets : ['Civelek', 'Muazzam'] },
  { name: 'Yael', age: 52, title : 'VP of Engineering'}
];

result = users
  .filter(has('pets'))
  .sort(sortBy('age')); 

  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

這些程式碼又是什麼意思?我們叫它箭頭原型的排列和濾波方法,每一種方法都使用一個單功能引數,但是我們會呼叫返回函式的函式來篩選和排序,而不是編寫表示式去做這些。

讓我們來看一看,在下列每種情況下返回函式的表達方式。

無高階函式:

result = users
  .filter(x => x.hasOwnProperty('pets')) //pass Function to filter
  .sort((a, b) => a.age > b.age);        //pass Function to sort

  
  • 1
  • 2
  • 3

有高階函式:

result = users
  .filter(has('pets'))  //pass Function to filter
  .sort(sortBy('age')); //pass Function to sort

  
  • 1
  • 2
  • 3

在每個例項中,過濾過程都是通過檢查物件是否含有名為“pets”的屬性值的函式執行的。

為什麼它是有用的?

它的有效性出於以下幾個原因:

  • 它減少了重複程式碼
  • 它簡化了程式碼的可重用性
  • 它提升了程式碼含義的清晰度

想象一下我們只想過濾出有寵物和有職位的使用者,我們可以在其中新增另一個函式:

result = users
  .filter(x => x.hasOwnProperty('pets'))
  .filter(x => x.hasOwnProperty('title'))
  ...

  
  • 1
  • 2
  • 3
  • 4

這裡的重複只是徒增雜亂:它的簡明度沒有提升,只是寫了更多程式碼妨礙視線。下面是可實現同樣功能的使用了has函式的程式碼:

result = users
  .filter(has('pets'))
  .filter(has('title'))
  ...

  
  • 1
  • 2
  • 3
  • 4

這段程式碼更加短小精悍且易於書寫,還可以減少打錯字的情況。筆者同時認為這段程式碼大大增加了閱讀清晰度,一眼就能讀出程式碼的含義。
至於函式重用性,如果需要在很多地方過濾出有寵物的使用者或者有職位的使用者,你可以建立如下函式,並按需重用:

const hasPets = has('pets');
const isEmployed = has('title');
const byAge = sortBy('age');

let workers = users.filter(isEmployed);
let petOwningWorkers = workers.filter(hasPets);
let workersByAge = workers.sort(byAge);

  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

我們也可以使用已給出的函式來獲得單返回值,並不僅僅是用於過濾陣列:

let user = {name: 'Assata', age: 68, title: 'VP of Operations'};
if(isEmployed(user)){   // true
  //do employee action
}
hasPets(user);          // false
has('age')(user);       //true

  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

更進一步

讓我們來寫一個能檢查物件擁有一個有固定主鍵的過濾函式。has函式可檢查主鍵,然而我們需要知道兩項值(主鍵和鍵值),而不是一項,來同時檢查值。下面請看一種方法:

//[p]roperty, [v]alue, [o]bject:
const is = p => v => o => o.hasOwnProperty(p) && o[p] == v;

// broken down:
// outer:  p => [inner1 function, uses p]
// inner1: v => [inner2 function, uses p and v]
// inner2: o => o.hasOwnProperty(p) && o[p] = v;

  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

此處,叫做“is”的函式可執行以下三件事:

  1. 取一個屬性名並且返回一個函式,該函式……
  2. 取一個函式值返回一個函式,該函式……
  3. 取一個物件,並檢測該物件是否有特定函式值的特定屬性,最終返回一個布林值。

下面是一個使用is函式來過濾使用者的示例:

const titleIs = is('title');
// titleIs == v => o => o.hasOwnProperty('title') && o['title'] == v;

const isContractor = titleIs('Contractor');
// isContractor == o => o.hasOwnProperty('contractor') && o['title'] == 'Contractor';

let contractors = users.filter(isContractor);
let developers  = users.filter(titleIs('Developer'));

let user = {name: 'Viola', age: 50, title: 'Actress', pets: ['Zak']};
isEmployed(user);   // true
isContractor(user); // false 

  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

書寫風格說明

看一眼下面這個函式,記下你弄清楚函式意義的時間:

const i = x => y => z => h(x)(y) && y[x] == z;

  
  • 1

現在請再看一下同功能僅在書寫風格中略微不同的函式:

const is = prop => val => obj => has(prop)(obj) && obj[prop] == val;

  
  • 1

可見,書寫一行越簡潔越好的函式的編碼趨勢是以犧牲可讀性為代價的。請剋制自己這樣做的衝動!簡短卻無意義的變數名的確看起來很漂亮,但是讓人難以理解函式的功能。起一個包含多個詞的有意義的變數名和函式名,其實是在幫你自己和同事理解函式功能。

還有一件事…

如果你想以年齡降序排序而不是升序排列,該怎麼做呢?或者說查詢不是僱員的使用者該怎麼做呢?我們需要去寫一個新的functionssortByDesc 或者notHas程式嗎?當然不用!我們可以將已有的返回布林值的函式封裝起來,用一個反轉其布林值的函式來實現上述功能,反之亦然。

//take args, pass them thru to function x, invert the result of x
const invert = x => (...args) => !x(...args);
const noPets = invert(hasPets);

let petlessUsersOldestFirst = users
  .filter(noPets)
  .sort(invert(sortBy('age')));

  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

總結

函數語言程式設計在程式設計界的勢頭越來越強勁,而ES6使得在JavaScript中採用這種程式設計思想更加容易。如果你在JavaScript程式設計過程中還沒遇到過函數語言程式設計風格的程式碼,你很可能在接下來的幾個月裡就能遇到。這意味著即便你不喜歡這種風格,理解它的基礎知識也是非常重要的,有的知識在這裡已經提到過了。希望這篇文章提出的概念能夠幫你在實際遇到ES6程式碼時準備好前提知識,更希望它能鼓勵你嘗試這種程式設計風格!

OneAPM 助您輕鬆鎖定 .NET 應用效能瓶頸,通過強大的 Trace 記錄逐層分析,直至鎖定行級問題程式碼。以使用者角度展示系統響應速度,以地域和瀏覽器維度統計使用者使用情況。想閱讀更多技術文章,請訪問 OneAPM 官方部落格
本文轉自 OneAPM 官方部落格
原文連結:https://strongloop.com/strongblog/higher-order-functions-in-es6easy-as-a-b-c/