摘要:什么是听起来很酷的一等公民?
《用简单而深入的语言解释JavaScript》系列:
我看到上面提到的一篇关于JavaScript历史的文章:JavaScript借用了Scheme语言,将函数提升到了“一等公民”的地位。
一等公民这个名字听起来很宏伟,但也相当冗长。 这与翻译无关,因为包括我在内的很多一等公民都不知道该说什么。
JavaScript 函数是一等公民是什么意思? 让我向您解释一下并给您一些想法。
一等公民的定义
据维基百科介绍,编程语言中的一等公民概念早在 20 世纪 60 年代就由荷兰计算机科学家 Christopher Strachey 提出。 那时,没有个人电脑,没有互联网,没有浏览器,也没有 JavaScript。
可能很多人和我一样,没有听说过克里斯托弗·斯特雷奇(Christopher Strachey)javascript声明函数,他只是提出了一等公民的概念,没有给出严格的定义。
关于一等公民,我从《编程语言语用学》一书中找到了一个权威的定义javascript声明函数,它是很多院校编程语言设计的教材。
一般来说,如果编程语言中的值可以作为参数传递、从子例程返回或分配给变量,则该值被称为具有第一类状态。
也就是说,在编程语言中,一等公民可以用作函数参数,也可以用作函数返回值,也可以用作变量的形式参数。
例如,字符串在几乎所有编程语言中都是一等公民。 字符串可以用作函数参数,字符串可以用作函数返回值,字符串还可以用作变量的形式参数。
对于各种编程语言来说,函数不一定是一等公民,比如Java 8之前的版本。
对于JavaScript来说,函数可以给变量赋予形式参数,也可以作为函数参数,还可以作为函数返回值,所以JavaScript中的函数是一等公民。
函数作为函数参数
回调函数(callback)是JavaScript异步编程的基础。 它实际上使用函数作为函数参数。 比如常用的setTimeout函数的第一个参数就是函数:
setTimeout(function() {
console.log("Hello, Fundebug!");
}, 1000);
JavaScript函数作为函数参数,或者回调函数,作为异步的一种形式,大家都写了很多。 其实它还有其他的应用场景。
在对一些复杂的数据结构进行排序时,可以使用自定义的比较函数作为参数:
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函数的定义中,还要求该函数可以存储在其他数据结构中,例如链表和对象,JavaScript也支持这些数据结构。
在计算机科学中,如果一种编程语言将函数视为一等公民,则该语言被称为具有一流函数。 这意味着该语言支持将函数作为参数传递给其他函数,将它们作为其他函数的值返回,并将它们分配给变量或将它们存储在数据结构中。
函数可以保存到Object,这意味着函数成为一个Object。 我在《JavaScript第一课:箭头函数中的this到底是什么?》中写到 》中提到,当函数作为对象调用时,它的 this 值就是该对象。 这与Java等面向对象语言是一致的。 因此,在JavaScript存在之前,它在一定程度上支持面向对象编程,但当然是比较弱的。
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。 即使网页上有窗口,也最好不要使用它,因为它会污染全局范围。 这个时候我们应该做什么呢? 让我向您展示如何定义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,通过return返回函数中定义的testApi函数。
这里虽然也使用了闭包,但是在defineTestApi函数执行完成后,testApi函数仍然可以使用config变量,所以config变量的生命周期已经超过了defineTestApi函数。 我将在本系列后续文章中详细介绍闭包。
因此,函数内返回函数还是很有用的。
开发者在处理每一个技术点,比如闭包时,都应该保持谦虚的态度,不要认为这个没用,那个没用。 其实只是你还没有遇到使用场景而已。 关于这一点,大家可以看看我的博客《一边批评个别评论一边谈我的第一篇10万+文章》。
函数作为第一公民是函数式编程的基础
我已经介绍了使职能成为第一公民的三个特征。 它们确实让 JavaScript 变得更加强大。 接下来是什么? 你已经见过很多 JavaScript 的炫酷操作,你不会认为它们有什么神奇之处。
事实上,函数是第一公民,与大家都听说过的函数式编程密切相关。
一等函数是函数式编程风格的必需品,其中使用高阶函数是标准做法。
换句话说,函数作为第一公民是函数式编程的必要条件。 高阶函数是使用函数作为参数的函数。 它们在函数式编程中非常常见。
至于什么是函数式编程,我无法用一句话解释清楚。 我们总能谈论计算机鼻祖图灵。 想知道接下来会发生什么,请听下一章。
关于JS,我准备开始写一系列的博客。 您还有什么不清楚的地方吗? 为什么不发表评论,以便我可以研究并与您分享。 也欢迎您添加我个人的Momo(KiwenLau)。 我是Fundebug的技术总监,一个对JS又爱又恨的程序员。
关于 Fundebug 的参考
Fundebug专注于JavaScript、微信小程序、微信小游戏、支付宝小程序、React Native、Node.js和Java在线应用的实时BUG监控。 自2016年双十一即将上线以来,Fundebug已累计处理超过10亿次错误事件。 付费客户包括阳光保险、核桃编程、荔枝FM、掌门1对1、微脉、青团等多家品牌公司。 欢迎您免费试用!