函数式编程
1. 什么是函数式编程
函数式编程(英语:functional programming)或称函数程序设计、泛函编程,是一种编程范式,它将计算机运算视为函数运算,并且避免使用程序状态以及易变对象。即对过程进行抽象,将数据以输入输出流的方式封装进过程内部,从而也降低系统的耦合度。
非函数式编程
myString="my name is String"
var words = [],
count = 0;
text = myString.split(" ");
for (i = 0; count < 4, i < text.length; i++) {
if (!text[i].match(/[0-9]/)) {
words = words.concat(text[i]);
count++;
}
}
console.log(words); // ["my", "name", "is", "String"]
函数式编程
myString="my name is String"
var words = myString
.split(" ")
.filter(function(x) {
return !x.match(/[1-9]+/);
})
.slice(0, 4);
console.log(words); // ["my", "name", "is", "String"]
2. 纯函数
“函数”是输入与输出之间的关系,即对于相同的输入,永远会得到相同的输出,而且没有任何可观察的副作用,也不依赖外部环境的状态
var messages = ["Hi", "Hello", "Sup", "Hey", "Hola"];
messages
.map(function(s, i) {
return printSomewhere(s, 100 * i * 10, 100 * i * 10);
})
.forEach(function(element) {
document.body.appendChild(element);
});
3. lambda函数又称匿名函数
如果函数只需要引用一次,则无需浪费函数名
匿名函数lambda:lambda表达式基于数学中的λ演算得名,直接对应于其中的lambda抽象。是指一类无需定义标识符(函数名)的函数或子程序。所谓匿名函数,通俗地说就是没有名字的函数,lambda函数没有名字,是一种简单的、在同一行中定义函数的方法
// 在JavaScript中
const lamFun = (x)=> {return x+1}
// 在JAVA中
() -> System.out.println("hello world");
// 在Python中
fun = lambda x, y: x * y
print(fun(2, 3)) // 6
4. abamdan柯里化
所谓"柯里化",就是把一个多参数的函数,转化为单参数函数。
// 柯里化之前
function add(x, y) {
return x + y;
}
add(1, 2) // 3
// 柯里化之后
function addX(y) {
return function (x) {
return x + y;
};
}
addX(2)(1)
5. 函数合成(compose)
如果一个值要经过多个函数,才能变成另外一个值,就可以把所有中间步骤合并成一个函数,这叫做"函数的合成"(compose)。
合成也是函数必须是纯的一个原因
var compose = function(f,g) {
return function(x) {
return f(g(x));
};
};
var toUpperCase = function(x) { return x.toUpperCase(); };
var exclaim = function(x) { return x + '!'; };
var shout = compose(exclaim, toUpperCase);
shout("send in the clowns"); // "SEND IN THE CLOWNS!"
6. 函子、容器Functor
简单的例子
var Container = function(x) {
this._value = x
}
Container.of = x => new Container(x)
Container.of(1)
Container.prototype.map = function(f){
return Container.of(f(this._value))
}
class Functor {
constructor(val) {
this.val = val;
}
map(f) {
return new Functor(f(this.val));
}
}
Functor是一个函子,它的map方法接受函数f作为参数,然后返回一个新的函子,里面包含的值是被f处理过的(f(this.val))
一般约定,函子的标志就是容器具有map方法。该方法将容器里面的每一个值,映射到另一个容器。
尾调用:指某个函数的最后一步是调用另一个函数
7. 自调用函数和闭包
闭包指的是有权访问父作用域的函数,即使在父函数关闭之后。
function makeAdder(x) {
return function(y) {
return x + y;
};
}
var add5 = makeAdder(5);
var add10 = makeAdder(10);
console.log(add5(2)); // 7
console.log(add10(2)); // 12
8. 递归
欧几里得算法,寻找两个数的最大公分母
function gcd(a, b) {
if (b == 0) {
// base case (conquer)
return a;
} else {
// recursive case (divide)
return gcd(b, a % b);
}
}
console.log(gcd(12, 8));
console.log(gcd(100, 20));
9. 高阶函数
函数可以作为函数被传入,也称为回调函数,如函数合成运算。
可以返回函数作为输出,如函数柯里化运算。
高阶函数是一个接收函数作为参数或将函数作为输出返回的函数
add = (x) => (y) => x + y
add(10) //(y) => x + y
add(10)(20) // 30
高阶函数的复杂实例
function powersOf(x) {
return function(y) {
// this is an anonymous function!
return Math.pow(x, y);
};
}
powerOfTwo = powersOf(2);
console.log(powerOfTwo(1)); // 2
console.log(powerOfTwo(2));
缺点:在堆栈中难以识别,不方便调试
荣誉函数
map() filter() reduce() 这三个函数为函数式编程带来便利
实现一个类似Map的高阶函数
function mapForEach(arr,fn){
const newArray = []
for(let i=0;i{ return item.length })
console.log(arrMap) //[3, 5, 6, 4, 5]
10. 学习完自己的代码优化实例
多维数组遍历
let arr4 = [{name:'one',children:[{name:'two',children:[{name:'three'}]}]}]
function renderList(arrs){
const list = []
function renderArr(arr=[]){
arr.forEach(item=>{
list.push({name:item.name})
if(item.children && item.children.length){
renderArr(item.children)
}
})
}
renderArr(arrs)
return list
}
console.log('lists',renderList(arr4))
// [0: {name: "one"},1: {name: "two"},2: {name: "three"}]
源数据结构如下:
const data = [{
id: 5,
label: "工业",
children:[
{ id: 10012,
label: "塑料加工",
children:[
{id: 10000129,label: "共混料"},
{id: 10000130,label: "母料(色母等)"},
{id: 10000301,label: "木塑复合材料"}
]
}
]
}]
未优化前
renderTreeList ({ list = [], isOpen = false, rank = 0, parentId = [] }) {
list.forEach(item => {
this.treeList.push({
id: item.id,
label: item.label,
rank,
parentId,
isOpen: false,
show: rank === 0,
checked: this.selectedIds.includes(item.id)
})
if (Array.isArray(item.children) && item.children.length > 0) {
let parents = [...parentId]
parents.push(item.id)
this.renderTreeList({ list: item.children, rank: rank + 1, parentId: parents })
} else {
this.treeList[this.treeList.length - 1].lastRank = true
}
})
this.treeList = [...this.isDefaultOpen({ arr: this.treeList, selectedIds: this.selectedIds, isOpen })]
}
优化后
renderList (arrs) {
const arrList = []
const renderArr = function ({ list = [], isOpen = false, rank = 0, parentId = [] }) {
list.forEach(item => {
arrList.push({
id: item.id,
label: item.label,
rank,
parentId,
isOpen: false,
show: rank === 0,
checked: true
})
if (Array.isArray(item.children) && item.children.length > 0) {
let parents = [...parentId]
parents.push(item.id)
renderArr({ list: item.children, rank: rank + 1, parentId: parents })
} else {
arrList[arrList.length - 1].lastRank = true
}
})
}
renderArr({ list: arrs })
return arrList
}
es6数组拉平方法
https://es6.ruanyifeng.com/?search=%E4%B8%80%E7%BB%B4&x=0&y=0#docs/array#%E6%95%B0%E7%BB%84%E5%AE%9E%E4%BE%8B%E7%9A%84-flat%EF%BC%8CflatMap
总结:
函数式编程的好处:
1.不容易产生bug,方便测试和并行处理;
2.可以抛弃this,避免被this指向弄晕;
3.打包过程中可以更好的利用 tree shaking 过滤无用代码;
4.有很多库可以帮助我们进行函数式开发,比如经典的lodash。
缺点:
1.存在性能问题,Map、filter这些需要遍历多次,增加时间开销
2.资源占用,在 JS 中为了实现对象状态的不可变,往往会创建新的对象,因此,它对垃圾回收所产生的压力远远超过其他编程方式