JavaScript深刻淺出第2課:函數是一等公民是什麼意思呢?

JavaScript深刻淺出》系列javascript

看到一篇講JavaScript歷史的文章裏面提到:JavaScript借鑑Scheme語言,將函數提高到"一等公民"(first class citizen)的地位html

一等公民這個名字聽起來很高大上,可是也至關晦澀,這個與翻譯也沒什麼關係,由於first class citizen不少人包括我也不知所云。java

JavaScript函數是一等公民,是什麼意思呢?我來與你們探討一下,拋磚引玉。程序員

一等公民的定義

根據維基百科,編程語言中一等公民的概念是由英國計算機學家Christopher Strachey提出來的,時間則早在上個世紀60年代,那個時候尚未我的電腦,沒有互聯網,沒有瀏覽器,也沒有JavaScript。es6

大概不少人和我同樣,沒據說過Christopher Strachey,而且他也只是提出了一等公民的概念,沒有給出嚴格的定義。算法

關於一等公民,我找到一個權威的定義,來自於一本書《Programming Language Pragmatics》,這本書是不少大學的程序語言設計的教材。編程

In general, a value in a programming language is said to have first-class status if it can be passed as a parameter, returned from a subroutine, or assigned into a variable.小程序

也就是說,在編程語言中,一等公民能夠做爲函數參數,能夠做爲函數返回值,也能夠賦值給變量微信小程序

例如,字符串在幾乎全部編程語言中都是一等公民,字符串能夠作爲函數參數,字符串能夠做爲函數返回值,字符串也能夠賦值給變量。api

對於各類編程語言來講,函數就不必定是一等公民了,好比Java 8以前的版本

對於JavaScript來講,函數能夠賦值給變量,也能夠做爲函數參數,還能夠做爲函數返回值,所以JavaScript中函數是一等公民。

函數做爲函數參數

回調函數(callback)是JavaScript異步編程的基礎,其實就是把函數做爲函數參數。例如,你們經常使用的setTimeout函數的第一個參數就是函數:

setTimeout(function() {
    console.log("Hello, Fundebug!");
}, 1000);
複製代碼

JavaScript函數做爲函數參數,或者說回調函數,做爲實現異步的一種方式,你們都寫得多了,其實它還有其餘應用場景。

Array.prototype.sort()在對一些複雜數據結構進行排序時,可使用自定義的比較函數做爲參數:

var employees = [
    { name: "Liu", age: 21 },
    { name: "Zhang", age: 37 },
    { name: "Wang", age: 45 },
    { name: "Li", age: 30 },
    { name: "zan", age: 55 },
    { name: "Xi", age: 37 }
];

// 員工按照年齡排序
employees.sort(function(a, b) {
    return a.age - b.age;
});

// 員工按照名字排序
employees.sort(function(a, b) {
    var nameA = a.name;
    var nameB = b.name;
    if (nameA < nameB) {
        return -1;
    }
    if (nameA > nameB) {
        return 1;
    }
    return 0;
});
複製代碼

這樣寫看起來沒什麼大不了的,可是對於JavaScript引擎來講就省事多了,由於它不須要爲每一種數據類型去實現一個排序API,它只須要實現一個排序API就夠了,至於數組元素大小怎麼比較,交給用戶去定義,用戶若是非得說2大於1,那也不是不能夠。

換句話說,若是Array.prototype.sort()只能實現簡單數據(好比Number與String)的排序的話,那它就太弱了,正由於可使用函數做爲參數,使它的功能強大了不少。

順便提一下,實現一個Array.prototype.sort(),可不是什麼簡單的事情,你們能夠看看V8是怎樣實現數組排序的

將函數賦值給變量

JavaScript是能夠定義匿名函數的,當咱們定義有名字的函數時,一般是這樣寫的:

function hello() {
    console.log("Hello, Fundebug!");
}
複製代碼

固然,也能夠將函數賦值給變量:

var hello = function() {
    console.log("Hello, Fundebug!");
};

console.log(typeof hello); // 打印 function
複製代碼

可知,hello變量的類型是"function"。

在其餘的一些First-class function的定義中,還要求函數能夠保存到其餘數據結構,好比數組和對象中,這一點JavaScript也是支持的。

In computer science, a programming language is said to have first-class functions if it treats functions as first-class citizens. This means the language supports passing functions as arguments to other functions, returning them as the values from other functions, and assigning them to variables or storing them in data structures.

函數能夠保存到Object中,就意味着函數成爲了Object的方法。我在《JavaScript深刻淺出第1課:箭頭函數中的this到底是什麼鬼?》中提過,當函數做爲Object的方法被調用時,它的this值就是該Object,這1點與Java等面嚮對象語言是一致的。所以JavaScript在沒有Class以前,就在必定程度上是支持面向對象編程的,固然比較弱。

var person = {
    name: "Wang Lei",
    age: 40,
    greeting: function() {
        console.log(`Hello! My Name is ${this.name}.`);
    }
};

console.log(person.age); // 打印 40
person.greeting(); // 打印 Hello! My Name is Wang Lei.
複製代碼

函數做爲函數返回值

一般來說,函數的返回值比較簡單,好比數字、字符串、布爾值或者Object。因爲JavaScript函數是第一公民,所以咱們也能夠在函數中返回函數。

function sayHello(message) {
    return function() {
        console.log(`Hello, ${message}`);
    };
}

var sayHelloToFundebug = sayHello("Fundebug!");
var sayHelloToGoogle = sayHello("Google!");

sayHelloToFundebug(); // 打印Hello, Fundebug!
sayHelloToGoogle(); // 打印Hello, Google!
複製代碼

當咱們調用sayHello函數時,它返回值sayHelloToFundebug實際是一個函數,咱們須要調用所返回的sayHelloToFundebug函數,它纔會執行,打印對應的信息:"Hello, Fundebug!"。

我猜這個地方有人會擡槓,由於示例代碼沒有必要這麼寫,由於有更簡單的寫法:

function sayHello(message) {
    console.log(`Hello, ${message}`);
}

sayHello("Fundebug!"); // 打印Hello, Fundebug!
sayHello("Google!"); // 打印Hello, Google!
複製代碼

可是這只是一個簡單的示例,在一些複雜的實際場景中,在函數返回函數仍是頗有用的。下面給你們一個簡單的示例。

咱們Fundebug在微信小程序BUG監控插件的時候,把不一樣API的定義拆分在不一樣的文件,可是這些API須要共享一些全局屬性,好比用戶的個性化配置。微信小程序是沒有全局變量window的,就算是網頁端有window其實最好也不要用,會污染全局做用域。這時候該怎麼辦?給你們看看定義fundebug.test()是怎樣定義的吧:

function defineTestApi(config) {
    function testApi(name, message) {
        const event = {
            type: "test",
            apikey: config.apikey,
            name: name || "Test",
            message: message || "Hello, Fundebug!"
        };
        sendToFundebug(event);
    }
    return testApi;
}
複製代碼

咱們使用了一個外層函數defineTestApi來共享全局配置對象config,函數中定義的testApi函數則經過return返回。

這裏其實也用到了閉包,由於defineTestApi函數執行結束以後,testApi函數仍然可使用config變量,所以config變量的生命週期超越了defineTestApi函數。關於閉包的詳細介紹,我會在這個系列的後續文章中介紹。

所以,在函數中返回函數,仍是頗有用的

開發者對待每個技術點,好比閉包,應該保持謙卑,不要以爲這個也沒有用,那個也沒有用,其實只是你還沒遇到使用場景而已。關於這一點,你們能夠看看個人博客《聊聊個人第一篇10萬+,同時反駁某些評論》

函數爲第一公民是函數式編程的基礎

函數爲第一公民的3個特性我都介紹了,它們確實讓JavaScript更增強大,而後呢?JavaScript的騷操做你們見得多了,也不會以爲有什麼神奇之處。

其實,函數是第一公民,與你們都聽過的函數式編程有着密切的關係。

First-class functions are a necessity for the functional programming style, in which the use of higher-order functions is a standard practice.

也就是說,函數爲第一公民是函數式編程的必要條件。higher-order functions,即高階函數,就是使用函數做爲參數的函數,它在函數式編程中很常見。

至於什麼是函數式編程,不是我一句話能講清楚的,這能夠一直聊到計算機的開山鼻祖圖靈。要知後事如何,請聽下回分解。

關於JS,我打算開始寫一個系列的博客,你們還有啥不太清楚的地方?不妨留言一下,我能夠研究一下,而後再與你們分享一下。也你們歡迎添加個人我的微信(KiwenLau),我是Fundebug的技術負責人,一個對JS又愛又恨的程序員。

參考

關於Fundebug

Fundebug專一於JavaScript、微信小程序、微信小遊戲、支付寶小程序、React Native、Node.js和Java線上應用實時BUG監控。 自從2016年雙十一正式上線,Fundebug累計處理了10億+錯誤事件,付費客戶有陽光保險、核桃編程、荔枝FM、掌門1對一、微脈、青團社等衆多品牌企業。歡迎你們免費試用

img

版權聲明

轉載時請註明做者 Fundebug以及本文地址:

blog.fundebug.com/2019/06/25/…

相關文章
相關標籤/搜索