JavaScript深刻浅出第2课:函数是一等公民是什么意思呢?

摘要: 听起来很炫酷的一等公民是啥?javascript

《JavaScript深刻浅出》系列html

看到一篇讲JavaScript历史的文章里面提到:JavaScript借鉴Scheme语言,将函数提高到"一等公民"(first class citizen)的地位java

一等公民这个名字听起来很高大上,可是也至关晦涩,这个与翻译也没什么关系,由于first class citizen不少人包括我也不知所云。程序员

JavaScript函数是一等公民,是什么意思呢?我来与你们探讨一下,抛砖引玉。es6

一等公民的定义

根据维基百科,编程语言中一等公民的概念是由英国计算机学家Christopher Strachey提出来的,时间则早在上个世纪60年代,那个时候尚未我的电脑,没有互联网,没有浏览器,也没有JavaScript。编程

大概不少人和我同样,没据说过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以及本文地址:

https://blog.fundebug.com/2019/06/25/javascript-first-class-function/

相关文章
相关标签/搜索