JavaScript 中的一等函數

Javascript 做爲一等函數。是一個高級開發者應該掌握的。爲何會被稱做一等?不只僅是它能夠被當作變量同樣處理。下面有三個關鍵點表明了 Javascript 一等函數的特色:javascript

  1. 函數能夠被複制給變量(函數聲明與函數表達式的區別)
  2. 函數能夠被當作參數傳遞給另外一個函數(高階函數)
  3. 函數能夠被另外一個函數返回(高階函數,閉包)

針對以上特色,咱們來作幾個真實的複雜的例子。java

給變量賦值一個函數

咱們建立一個返回文本 「Hello」 的函數,而後把該函數賦值給變量 sayHelloios

const sayHello = () => {
  return 'Hello';
};

console.log(sayHello());
// "Hello"
複製代碼

這裏有個區別是關於函數聲明和函數表達式的區別。這個 StackOverflow 問題已經將其解釋的清清楚楚。推薦有英文基礎的同窗瞭解一下。其實答案也很簡單,他們之間的區別在於變量提高(hoisting)。具體到複雜的生成環境中,這個狀況仍是須要多多注意的,加上代碼結構的影響,什麼時候使用函數聲明,什麼時候使用表達式,是一個須要多多思考的問題。好比:在 if 表達式中,使用函數聲明是無效的,會被提高到 if 以外,而使用 let 相關的表達式則能夠限定該函數的做用範圍。面試

做爲參數傳遞函數

使用以前提到的 sayHello 方法,把該方法當作參數傳遞給 sayHelloToPerson。在函數 sayHelloToPerson 中,變量 greeter 將會指向 sayHello 方法的內存地址。當咱們調用 greeter()時,sayHello就被調用了。axios

const sayHelloToPerson = (greeter, person) => {
  return greeter() + ' ' + person;
};

console.log(sayHelloToPerson(sayHello, 'John'));
// Hello John
複製代碼

單純的說這個做爲參數傳遞函數有點簡單了。咱們再舉一個你們必定用過的例子:api

let array = [1,2,3,4,5,6];
array.map(item => item * 2);
複製代碼

當你想對一個數組進行一些格式化或者改寫的時候, map 是最容易想到的方法。map 裏面其實就是接受了一個匿名函數,這個函數就是被當作參數傳入的,只是匿名而已。同時,map 的使用方法也是一種高階函數的表現。怎麼樣,是否是以爲一會兒高大上了許多。關於數組的方法,最值得研究的 reduce,這裏不展開討論,不過能夠出個題目供你們在留言區嘗試解決一下:如何使用reduce實現一個map?數組

從函數中返回一個函數

若是咱們不想輸出 Hello,而是想自定義建立任何輸出的形式。那麼咱們須要一個能夠建立輸出語的函數。閉包

const greeterMaker = greeting => {
  return person => {
    return greeting + ' ' + person;
  };
};

const sayHelloToPerson = greeterMaker('Hello');
const sayHowdyToPerson = greeterMaker('Howdy');

console.log(sayHelloToPerson('Joanne'));
// "Hello Joanne"

console.log(sayHowdyToPerson('Joanne'));
// "Howdy Joanne"
複製代碼

上圖中的 greeterMaker 接受了一個參數而且把該參數做爲返回的一個匿名函數的參數。經過傳遞的參數來定義問候語。咱們在使用 jQuery 時,經常看到的 $(this).find().eq(0) 這樣鏈式調用的寫法,其關鍵在於每一個方法都返回了當前的 this 對象。這個思路與上面返回函數也有必定的類似之處。函數

真實的案例

對象校驗

假設有一組由對象構成的用戶信息數據,須要咱們校驗。咱們能夠經過校驗函數來判斷,這些數據是否合法。oop

const usernameLongEnough = obj => {
  return obj.username.length >= 5;
};

const passwordsMatch = obj => {
  return obj.password === obj.confirmPassword;
};

const objectIsValid = (obj, ...funcs) => {
  for (let i = 0; i < funcs.length; i++) {
    if (funcs[i](obj) === false) {
      return false;
    }
  }

  return true;
};

const obj1 = {
  username: 'abc123',
  password: 'foobar',
  confirmPassword: 'foobar',
};

const obj1Valid = objectIsValid(obj1, usernameLongEnough, passwordsMatch);
console.log(obj1Valid);
// true

const obj2 = {
  username: 'joe555',
  password: 'foobar',
  confirmPassword: 'oops',
};

const obj2Valid = objectIsValid(obj2, usernameLongEnough, passwordsMatch);
console.log(obj2Valid);
// false
複製代碼

API key 閉包

假設須要使用 API key 來調用 API。咱們須要給每個請求添加這個 key。那麼咱們能夠建立一個方法把這個 API key 做爲參數保存在一個方法中,而後返回這個方法。

const apiConnect = apiKey => {
  const getData = route => {
    return axios.get(`${route}?key=${apiKey}`);
  };

  const postData = (route, params) => {
    return axios.post(route, {
      body: JSON.stringify(params),
      headers: {
        Authorization: `Bearer ${apiKey}`,
      },
    });
  };

  return { getData, postData };
};

const api = apiConnect('my-secret-key');

// No need to include the apiKey anymore
api.getData('http://www.example.com/get-endpoint');
api.postData('http://www.example.com/post-endpoint', { name: 'Joe' });
複製代碼

總結

Javascript 中的函數還承擔着實現類的功能。儘管 Javascript 中沒有真正的類,它是基於原型繼承的方式來實現封裝、繼承和多態的特性。關於這裏的只是,在【Javascript高級程序設計】一書中有詳細講解。函數是一類特殊的對象。咱們來看看下面的例子來解釋這個緣由:

console.log(Function instanceof Object);
// true
console.log(Object instanceof Function);
// true
複製代碼

函數是一類特殊的對象,那麼 Function 做爲 Object 的一個實例是正常的。不只是 FunctionStringArrayNumber 等,都是 Object 的實例。然而 Object instanceof Function 該作如何解釋? 實際上,全部的構造函數,本質上仍是函數。一個函數必然是 Function 的實例啦!上面這個問題曾經是我遇到的面試題。可是沒有答對。仍是理解不深入。在這裏總結反思一下,查漏補缺。

參考資料

  1. first-class-functions-in-javascript

pic
相關文章
相關標籤/搜索