我认为函数式编程的本质是把函数当作变量来使用。最近接触了React
,我们可以发现在React
中处处存在函数式编程的思想,我们将JSX
写成一个函数的返回值,并且在render
中调用她们。函数是对于过程的抽象,我们将过程抽象成一个函数,然后根据不同的场景传入不同的变量值从而得到不同的结果。而函数式编程是对于函数的抽象,我们把函数看作是一个变量,然后根据不同的场景传入不同的函数值从而得到不同的结果。
我希望可以通过实现一个小的demo
来讲清楚这件事,这个demo
是我很久以前在某个技术网站看到的,具体的网址已经记不清楚了,鉴于小伙伴们对于我所讲的函数式编程的概念非常感兴趣,所以我将这个例子翻了出来,自己将它重新实现一遍。若是没有讲清楚或错误的地方,则是受限于我的浅薄的技术功底,并希望被指正。
红绿灯函数
客户希望我们实现一个红绿灯函数,每隔1秒钟,变换一次信号,无限循环。可以使用console.log来模拟信号灯亮的过程,即在控制台打印'绿灯亮',1s后继续打印'黄灯亮',1s后打印'红灯亮'...无限循环
实现这个效果非常简单,直接上代码
function semaphores() {
setTimeout(()=>{
console.log('绿灯亮');
setTimeout(() => {
console.log('黄灯亮');
setTimeout(() => {
console.log('红灯亮');
semaphores();
}, 1000)
}, 1000)
},1000)
}
// delay 1s
// 绿灯亮
// delay 1s
// 黄灯亮
// delay 1s
// 红灯亮
// delay 1s
// ...
但是很明显,这样的函数及其不优雅,我们的红绿灯只有三种颜色,如果要是有十几种颜色的霓虹灯闪来闪去的话,我们就要嵌套十几层代码了。这样可读性实在是太差了,于是我们希望可以把它优化一下。
仔细分析一下我们的代码就会发现,上面的demo有非常多的冗余代码,setTimeout在一个函数中被重复使用了三次,每次只不过是一个嵌套再打印不同的红黄绿灯而已,我们为什么不把公共部分提取出来呢?把红黄绿灯提取成变量,代码质量会好很多。
so出现了下列的代码:
const TRAFFIC_LIGHT = [
'绿灯',
'黄灯',
'红灯'
]
function semaphores(lightList = [], count = 0) {
// 控制器
let lightLength = lightList.length;
return setTimeout(() => {
console.log(`${lightList[count % lightLength]}`);
count ++;
semaphores(lightList,count);
}, 1000)
}
// delay 1s
// 绿灯亮
// delay 1s
// 黄灯亮
// delay 1s
// 红灯亮
// delay 1s
// ...
我们还可以进一步优化,同时将时间抽离:
const TRAFFIC_LIGHT = [
['绿灯', 1000],
['黄灯', 2000],
['红灯', 3000]
]
function semaphores(lightList = [], count = 0) {
const lightLength = lightList.length;
return function handleSemaphores(){
const [light, time] = lightList[count % lightLength];
return setTimeout(() => {
console.log(`${light}亮`);
count ++;
handleSemaphores();
}, time)
}
}
// delay 1s
// 绿灯亮
// delay 2s
// 黄灯亮
// delay 3s
// 红灯亮
// delay 1s
// ...
我们可以更加容易修改我们的代码,以应对将来可能的需求变更。这也是我们常见的函数形式。变量只是 Number、String、Object、undefined、null、Boolean,但是我们再次将问题变得复杂一些:
我们的红绿灯效果良好,客户非常满意,并且希望扩展应用范围。为了应对可能到来的困惑,我们需要在每次信号灯亮起的时候添加上一条提示语,提醒剩余时间。不同的应用场景将有不同的提示语。
这也非常简单,我们只需要多加一条语句就可以完成:
const TRAFFIC_LIGHT = [
['绿灯', 1000],
['黄灯', 2000],
['红灯', 3000]
]
function semaphores(lightList = [], count = 0) {
const lightLength = lightList.length;
return function handleSemaphores(){
const [light, time] = lightList[count % lightLength];
return setTimeout(() => {
console.log(`${light}亮`);
// add here!!!
console.log('这是一条提示语');
count ++;
handleSemaphores();
}, time)
}
}
// delay 1s
// 绿灯亮
// 这是一条提示语
// delay 2s
// 黄灯亮
// 这是一条提示语
// delay 3s
// 红灯亮
// 这是一条提示语
// delay 1s
// ...
但随着我们的应用场景进一步增多,我们的红绿灯将广泛的用于医疗、运输、零售行业,对于这些行业我们需要在信号灯亮起时拥有不同的操作,有的是发出声响、有的是打印提示语、有的是发出警报、有的是打开开关...
那么我们是不是需要把上面的那份代码到处拷贝一下,然后修改位于16行的代码,分别把它修改为发声、打印、报警、开关控制...
显然一般的函数已经无法满足我们的需求了,这时候我们就需要函数式编程。
函数式编程
我上面只是为了引出函数式编程的例子,所以举例夸张了一些。事实上函数式编程并不是非常高大上的概念,它就和面向对象、面向过程一样,只是一种编程的范式,在实际中拥有着非常广泛的应用(例如JSX)
就像我一开始提到的那样,函数式编程的核心不过是将函数当作变量那样来使用。我们希望改造一下我们的红绿灯函数,使我们的函数在信号灯亮后,可以在不同的场景使用不同的参数,我们来设计一下这个函数.首先我们需要传入一个函数,所以我们在形参列表中加入一个handler
的变量,便于我们使用它。然后在我们希望使用它的地方使用就好了。
const TRAFFIC_LIGHT = [
['绿灯', 1000],
['黄灯', 2000],
['红灯', 3000]
]
// 请注意我们在这里加入了一个参数handler
function semaphores(lightList = [], handler, count = 0) {
const lightLength = lightList.length;
return function handleSemaphores(){
const [light, time] = lightList[count % lightLength];
return setTimeout(() => {
console.log(`${light}亮`);
// 在这里使用它
handler && handler(light, time);
count ++;
handleSemaphores();
}, time)
}
}
我们可以这样使用上面的函数:
// 我们只需要在不同的场景改变不同的handler就可以了
const handler = function (light, time) {
console.log('函数式编程好爽啊');
console.log(`${light}亮${time}毫秒~~`)
}
const doTrafficLight = semaphores(TRAFFIC_LIGHT, handler);
const trafficTimeHandler = doTrafficLight();
// delay 1s
// 绿灯亮
// 函数式编程好爽啊
// 绿灯亮1000毫秒~~
// delay 2s
// 黄灯亮
// 函数式编程好爽啊
// 黄灯亮2000毫秒~~
// delay 3s
// 红灯亮
// 函数式编程好爽啊
// 红灯亮3000毫秒~~
// ...
我们更可以优化一下上面的函数,使得不同等亮起时使用不同的行为函数:
const TRAFFIC_LIGHT = [
['绿灯', 1000, function() {
// do something...
}],
['黄灯', 2000, function() {
// do something...
}],
['红灯', 3000, function() {
// do something...
}]
]
// 我们去掉了handler!!!!
function semaphores(lightList = [], count = 0) {
const lightLength = lightList.length;
return function handleSemaphores(){
const [light, time, handler] = lightList[count % lightLength];
return setTimeout(() => {
console.log(`${light}亮`);
// 在这里使用它
handler && handler(light, time);
count ++;
handleSemaphores();
}, time)
}
}
// delay 1s
// 绿灯亮
// dosomething1...
// delay 2s
// 黄灯亮
// dosomething2...
// delay 3s
// 红灯亮
// dosomething3...
// ...
函数式编程的抽象程度更高,它是对于过程的抽象。如果没有传入的参数作为支撑,那么这个函数毫无意义,比较明显的例子是JavaScript
中的map
和reduce
。这是最主要的概念,剩下的概念则都是一些规定,帮助我们写出更好的更健壮的函数式函数,相信理解了我刚刚所说的例子,再去理解其他概念会容易许多。
网友评论