JavaScript深入浅出第2课:函数是一等公民是什么意思呢?
摘要: 听起来很炫酷的一等公民是啥?
《JavaScript深入浅出》系列:
看到一篇讲JavaScript历史的文章里面提到:JavaScript借鉴Scheme语言,将函数提升到"一等公民"(first class citizen)的地位。
一等公民这个名字听起来很高大上,但是也相当晦涩,这个与翻译也没什么关系,因为first class citizen很多人包括我也不知所云。
JavaScript函数是一等公民,是什么意思呢?我来与大家探讨一下,抛砖引玉。
一等公民的定义
根据维基百科,编程语言中一等公民的概念是由英国计算机学家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.
也就是说,在编程语言中,一等公民可以作为函数参数,可以作为函数返回值,也可以赋值给变量。
例如,字符串在几乎所有编程语言中都是一等公民,字符串可以做为函数参数,字符串可以作为函数返回值,字符串也可以赋值给变量。
对于各种编程语言来说,函数就不一定是一等公民了,比如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又爱又恨的程序员。
参考
- Javascript诞生记
- Is JavaScript a (true) OOP language?
- First-class functions in Java 8
- 《聊聊我的第一篇10万+,同时反驳某些评论》
关于Fundebug
Fundebug专注于JavaScript、微信小程序、微信小游戏、支付宝小程序、React Native、Node.js和Java线上应用实时BUG监控。 自从2016年双十一正式上线,Fundebug累计处理了10亿+错误事件,付费客户有阳光保险、核桃编程、荔枝FM、掌门1对1、微脉、青团社等众多品牌企业。欢迎大家免费试用!
版权声明
转载时请注明作者Fundebug以及本文地址:
https://blog.fundebug.com/2019/06/25/javascript-first-class-function/
JavaScript深入浅出第2课:函数是一等公民是什么意思呢?的更多相关文章
- JavaScript深入浅出第1课:箭头函数中的this究竟是什么鬼?
<JavaScript 深入浅出>系列: JavaScript 深入浅出第 1 课:箭头函数中的 this 究竟是什么鬼? JavaScript 深入浅出第 2 课:函数是一等公民是什么意 ...
- JavaScript深入浅出第5课:Chrome是如何成功的?
摘要: Chrome改变世界. <JavaScript深入浅出>系列: JavaScript深入浅出第1课:箭头函数中的this究竟是什么鬼? JavaScript深入浅出第2课:函数是一 ...
- JavaScript深入浅出第4课:V8引擎是如何工作的?
摘要: 性能彪悍的V8引擎. <JavaScript深入浅出>系列: JavaScript深入浅出第1课:箭头函数中的this究竟是什么鬼? JavaScript深入浅出第2课:函数是一等 ...
- JavaScript深入浅出第3课:什么是垃圾回收算法?
摘要: JS是如何回收内存的? <JavaScript深入浅出>系列: JavaScript深入浅出第1课:箭头函数中的this究竟是什么鬼? JavaScript深入浅出第2课:函数是一 ...
- javascript学习第四课函数
函数也是一种数据类型:function类型 所以函数也可当作一个数据作参数传递 三种函数的声明示例: 一般来讲,声明方式一和声明方式二比较常用,方式三比较少. 常用函数方式示例: 注意:虽然函数支持嵌 ...
- [笔记] imooc《JavaScript深入浅出》对象与函数
懒得做草稿了,习惯md也懒得扔印象笔记 主要是之前没去接触这一部分,就随手记下来了 创建对象的方法 对象字面量 new构造器/原型链 Object.create() 属性操作 属性读写(以及读写异常. ...
- 前端笔记知识点整合之JavaScript(四)关于函数、作用域、闭包那点事
一.自定义函数function 函数就是功能.方法的封装.函数能够帮我们封装一段程序代码,这一段代码会具备某一项功能,函数在执行时,封装的这一段代码都会执行一次,实现某种功能.而且,函数可以多次调用. ...
- 前端笔记之JavaScript(四)关于函数、作用域、闭包那点事
一.自定义函数function 函数就是功能.方法的封装.函数能够帮我们封装一段程序代码,这一段代码会具备某一项功能,函数在执行时,封装的这一段代码都会执行一次,实现某种功能.而且,函数可以多次调用. ...
- JS中的一等公民:函数
在JavaScript中,函数可以 作为值赋给一个变量 作为参数传递给另一个函数 作为另一个函数的返回值 所以我们说JavaScript的函数是“一等公民”. 赋值: var foo = functi ...
随机推荐
- 洛谷P1447 [NOI2010]能量采集(容斥)
传送门 很明显题目要求的东西可以写成$\sum_{i=1}^{n}\sum_{j=1}^m gcd(i,j)*2-1$(一点都不明显) 如果直接枚举肯定爆炸 那么我们设$f[i]$表示存在公因数$i$ ...
- python 之 匿名函数
5.14 匿名函数 lambda x , y : x+y 1 匿名的目的就是要没有名字,给匿名函数赋给一个名字是没有意义的 2 匿名函数的参数规则.作用域关系与有名函数是一样的 3 匿名函数的函数体通 ...
- 聊聊 Laravel 5.5 的 「自动发现」
ThinkSNS是什么? ThinkSNS(简称TS),一款全平台综合性社交系统,目前最新版本为ThinkSNS+.ThinkSNS V4 ThinkSNS[简]. 看了Taylor Otwell发表 ...
- 管理docker容器
如果在容器中启动sshd,存在开销和攻击面增大的问题.同时也违反了Docker所倡导的一个容器一个进程的原则. docker attach 37d61466c69e \\注意:如果在stdin中exi ...
- CentOS 7 部署 nginx-1.14.2
参考:http://www.linuxe.cn/post-168.html 链接:https://pan.baidu.com/s/1NzHIY7mYgHJ6yMF_rdd0ZQ 提取码:n8o9 下载 ...
- Python-9-赋值进阶
1.序列解包 同时给多个变量赋值 >>> x, y, z = 1, 2, 3 >>> print(x, y, z) 1 2 3 用这种方式还可以交换两个变量的值 ...
- 目前最全的浏览器/CSS选择器兼容性总结(2009-8-10更新)
2009年2月24日,Safari 4.0 beta版正式发布,Safari从它的3.2版本开始就已经支持所有的CSS选择器(包括最新的CSS3).不过为了方便大家的工作,下面提供了最新版本的CSS选 ...
- Codeforces 1111D(退背包、排列组合)
要点 优质题解 因为只有某type坏人全部分布在同一撇时,才能一次消灭.所以题目安排完毕后一定是type(x)和type(y)占一半,其余占另一半. 实际情况只有52*52种,则预处理答案 枚举某两种 ...
- jq:jQuery库文件jquery.scrollLoading.js使用方法
图片延迟加载,滚动到哪里加载到哪里: 1:头部加载库文件 <script type="text/javascript" src="/js/jquery.scroll ...
- CountDownLatch MyUncaughtExceptionHandler
package com.yd.wmsc.util; import java.text.SimpleDateFormat; import java.util.Date; import java.util ...