背景
在初入代码编程的初级程序员,如何评价你的代码简洁、可拓展、可维护。如何让你的代码保证在相同的输入,得到相同的输出。如何合理的编排代码,如何让代码在各种情况下运行而不出意外的结果,这个需要开发者有相当的开发经验需要有编码的指导思想。函数式编程是在前端开发中有着很重要的指导思想之一。函数式编程会指导你编写运行无副作用代码、指导你如何让你编写的代码更加健壮。
遍历一个数组
js
var arr = [10,20,30,40,50]
for(var i=0;i<arr.length;i++){
console.log("arr index : " + i + " arr value : " + arr[i])
}
//arr index :0 arr value :10
//arr index :1 arr value :20
//arr index :2 arr value :30
//arr index :3 arr value :40
//arr index :4 arr value :50
上面这段代码可以说是本人大学入学第一次写的遍历数组编程代码,意义重大并在今后的学习工作中受益匪浅。
譬如说:
js
var arr = [10,20,30,40,50]
for(var i=0;i<arr.length;i++){
setTimeout(()=>{
console.log("arr index : " + i + " arr value : " + arr[i])
},0)
}
//arr index : 5 arr value : undefined
意外地我得到了五条 arr index : 5 arr value : undefined 这样的输出
js
var arr = [10,20,30,40,50]
for(var i=0;i<arr.length;i++){
if(arr[i] == 20){
arr.shift()
}
console.log("arr index : " + i + " arr value : " + arr[i])
}
//index : 0 arr value : 10
//index : 1 arr value : 30 《得到30》
//...
var arr = [10,20,30,40,50]
for(var i=0;i<arr.length;i++){
console.log("arr index : " + i + " arr value : " + arr[i])
if(arr[i] == 20){
arr.shift()
}
}
//index : 0 arr value : 10
//index : 1 arr value : 20 《得到20》
//..
在代码位置不一样的运行上下文中,我意外地得到了不同的值
js
//过滤数组
let effArr = [10,20,30,40,50].filter( (val,idx) => val != 20)
// effArr : [10,30,40,50]
在使用for遍历的过程中,发现随着你的业务代码复杂度上升,你就不得不考虑如何处理代码运行过程中各个被引用的变量的关系,在读取或赋值外部变量时是否会对代码流程产生副作用。在日常开发过程中很有可能就在不甚了解代码逻辑的情况下忽略了副作用产生的不稳定因素,从而导致代码运行得到了意外的结果。
函数式编程思想 (个人想法)
在一次编码中使用一个或多个单一职责的纯函数组合成一个完整的逻辑代码,并通过对抽象数据流操作以减少副作用和状态改变为目的的一种编程方法
函数式编程优点
1、任务分解
在整体来看,将复杂业务功能拆解,拆解成单一职责模式下的分段函数代码,经过函数组合调用成一个完整的功能。每一个功能函数我们都可以单独看作为一个模块,每一个功能函数都能单独维护、替换、重构,以上面数组遍历为例子,高阶函数fliter专注于数据的遍历过滤并返回一个新的数组,在遍历过滤的回调function里面负责过滤逻辑业务。原始数组、fliter返回的新数组、业务回调function都是相对独立、可替换。
2、链式处理
函数式编程建议使用链式数据流的方式去处理数据,在一个完整业务功能前,先使用我们任务拆解的思维,以流水线的方式拼接我们的业务功能。举个例子:统计一个班学生每月人均读书本数
js
let students = [
{num:3,name:"jack"},
{num:2,name:"may"},
{num:1,name:"judi"},
{num:null ,name:"ali"}
]
//命令式编程
let total = 0
let averageNum = 0
for(var i =0; i<students.length ; i++){
if(!!students[0].num){
total += students[0].num
}
}
averageNum = total / students.length
//函数式编程
//引用lodash
_.chain(students)
.filter( item => !!item.num)
.map(item => item.num)
.mean()
在上面的例子当中我使用了lodash,做了一个链式调用。在链式调用的过程中可以看出函数式编程模块化加工数据,以链式传递加工数据,不引入新的变量、不产生副作用。
3、响应式范式编程
1、事件响应处理
举例:(引用自 Rx.js 官网) 下面的代码展示的是如何累加每次点击的鼠标 x 坐标
js
//命令式编程
var count = 0
var rate = 1000
var lastClick = Date.now() - rate
var button = document.querySelector('button')
button.addEventListener('click', (event) => {
if (Date.now() - lastClick >= rate) {
count += event.clientX
console.log(count) lastClick = Date.now()
}
})
//函数式编程
var button = document.querySelector('button');
Rx.Observable.fromEvent(button, 'click')
.throttleTime(1000)
.map(event => event.clientX)
.scan((count, clientX) => count + clientX, 0)
.subscribe(count => console.log(count));
2、异步编程
js
let observable = Rx.Observable.create((observer)=> {
setInterval(() => {
observer.next('hi')
}, 1000)
})
observable.subscribe(x => console.log(x))
//hi
在响应式编程方面,引入了Rxjs做数据流管理,Rxjs官网中有这么一个点击处理的例子,相比于传统的命令式编程,Rxjs的例子在响应式编程最后接收整个时间流的处理结果,并且这个结果并不会与处理过程的变量有相互的副作用。在异步编程中,响应式范式编程则显得更得心应手。响应式编程摆脱了传统的异步回调编程,我们可以在链式调用里面处理异步编程。当然在Promise也是在链式处理异步编程。
总结
函数式编程和命令式编程式都是我们的编程指导方法,命令式编程更贴近人的传统思想,而函数式编程则要求我们对数据进行抽象,对任务进行拆分,通过任务的编排、组装、处理。对我们日常编程有了更高的要求,上手的难度也更高、依赖的成本也更高。譬如上面用到的Rxjs数据流管理的库,越到到后面想要了解的边界就越深,在开发调试过程中的各种响应式处理也会让人头疼。但是困难不会是阻挡我们了解新的思想探索前端未来的绊脚石