函数式编程是一种编程范式,主要是利用函数把运算过程封装起来,通过组合各种函数来计算结果。
函数式编程有两个基本特点:
- 通过函数来对数据进行转换
- 链式编程 (通过串联多个函数来求结果)
常见特性:
- 声明式的代码
- 不可变变量**:** 任何修改都会生成一个新的变量
- 无副作用 **(函数只会用到传递给它的变量以及自己内部创建的变量,不会使用到其他变量,相同的输入一定会得到相同的输出)
**- 函数是一等公民:指的是函数与其他数据类型一样,可以赋值给其他变量,也可以作为参数,传入另一个函数,或者作为别的函数的返回值。
- 使用纯函数的代码绝不会更改或破坏全局状态,有助于提高代码的可测试性和可维护性
- 函数式编程采用声明式的风格,易于推理,提高代码的可读性。
常见的函数式编程模型:
1.闭包
如果一个函数内部引用了该函数作用域外的其他局部变量,那么该函数就是一个闭包。
闭包的用途: 可以定义一些作用域局限的持久化变量,这些变量可以用来做缓存或者计算的中间量等。
持久化局部变量
2.高阶函数
指的是以函数作为参数传递,或以函数作为返回值。 **像:map,filter,reduce等等这些也属于高阶函数,**使用高阶函数会让我们的代码更清晰简洁
const arr = [5, 7, 1, 8, 4];
const sum = arr.reduce((accumulator, currentValue) => accumulator + currentValue,0);
console.log(sum)//25
**debounce 和 throttle 函数也是高阶函数(闭包的使用)
防抖和节流的作用都是防止函数多次调用。区别在于,假设一个用户一直触发这个函数,且每次触发函数的间隔小于wait,防抖的情况下只会调用一次,而节流的情况会每隔一定时间(参数wait)调用函数
**
防抖 (t时间段内多次触发事件只执行一次)每次执行的时候判断timer是否有值,有值则clearTimeout清空定时器,并且重新开启定时器,直到指定时间间隔内没有触发事件时才会真正执行事件的回调
最常用于button,input
function debounce(fn, delay) {
let timer = null;
return function () {
const args = arguments;
if (timer) clearTimeout(timer);
timer = setTimeout(() => {
fn.apply(this, args); }, delay);
}
}
不希望非要等到事件停止触发 n 秒后才执行;
希望触发后立刻执行函数,n 秒内连续触发不执行,然后等到停止触发 n 秒后,才可以重新触发执行。
function debounce(func, wait, immediate) {
let timer;
return function () {
const context = this;
const args = arguments;
// 每次执行开始先清除上一次的定时器
clearTimeout(timer);
if (immediate) {
// 如果 timer 有值,表明 wait 秒内连续触发,所以不执行
// wait 秒后 timer 的值置为 null, 再次触发,可立即执行
var callNow = !timer;
timer = setTimeout(function () {
timer = null;
}, wait);
if (callNow) func.apply(context, args);
} else {
timer = setTimeout(function () {
func.apply(context, args);
}, wait);
}
};
}
节流(隔一定的时间触发一次)触发函数事件后,指定间隔内多次触发不会执行,超过指定的时间间隔,才能进行下一次的函数调用
最常用于resize,scroll事件中处理
// 使用时间戳
function throttle(func, wait) {
let preTime = 0;
return function () {
let nowTime = +new Date();
let context = this;
let args = arguments;
if (nowTime - preTime > wait) {
func.apply(context, args);
preTime = nowTime;
}
};
}
// 定时器实现
function throttle(func, wait) {
let timeout;
return function () {
let context = this;
let args = arguments;
if (!timeout) {
timeout = setTimeout(function () {
timeout = null;
func.apply(context, args);
}, wait);
}
};
}
const obj = {
a: 1,
func: function () {
console.log(this.a, arguments.length);
return this.a + arguments.length
}
}
const obj2 = {
a: 2
}
/********** call 开始 ***********/
function myCall (context, ...res) {
if (typeof this !== 'function') {
throw new TypeError('not funciton')
}
context = context || window
context.fn = this;
let result = context.fn(...res);
delete context.fn;
return result;
}
/************* call 结束 ************/
Function.prototype.myCall = myCall
obj.func.myCall(obj2,1,2);
/********** apply 开始 ***********/
function myApply(context, params) {
if(typeof this != 'function') {
throw new TypeError('not function')
}
context = context || window;
context.fn = this;
return context.fn(...params);
}
/*********** apply 结束 ************/
Function.prototype.myApply = myApply;
obj.func.myApply(obj2,[1,2,3]);
/********** bind 开始 ***********/
function myBind(context, ...res) {
const fn = this;
return function (...args) {
return fn.apply(context,[...res, ...args]) }
}
/************ bind 结束 ***********/
Function.prototype.myBind = myBind
const func2 = obj.func.myBind(obj2, 1)
func2(1,2)
3.函数柯里化(currying)
又称部分求值,柯里化函数会接收一些参数,然后不会立即求值,而是继续返回一个新函数,将传入的参数通过闭包的形式保存,等到被真正求值的时候,再一次性把所有传入的参数进行求值。
柯里化的作用:参数复用;延迟计算
参数复用
const _ = require("lodash");
const buildUriCurry = _.curry(buildUri);
const myGithubPath = buildUriCurry("https", "github.com");
const profilePath = myGithubPath("semlinker/semlinker");
const awesomeTsPath = myGithubPath("semlinker/awesome-typescript");
// 延迟计算
var add = function(x) {
return function(y) {
return x + y;
};
};
var increment = add(1);
increment(2); // 3
const curry = function (fn) {
let params = []
const func = (...args) => {
params = params.concat([...args])
if(params.length >= fn.length) {
return fn(...params)
} else {
return func
}
}
return func
}
const add = function (a,b,c) {
return [...arguments].reduce((sum, num) => sum += num, 0)
}
// add(1,2,3)
const curryAdd = curry(add)
curryAdd(1)(2)(3)
function curry(func) {
return function curried(...args) {
if (args.length >= func.length) { // 通过函数的length属性,来获取函数的形参个数
return func.apply(this, args);
} else {
return function (...args2) {
return curried.apply(this, args.concat(args2));
};
}
}
}
4.函数组合(Composition)
随着链式编程的数量增多,代码的可读性就会不断下降,函数组合可以用来解决这个问题;
为了实现函数的复用,我们通常会尽量保证函数的职责单一,通过对函数进行组合,来实现特定的功能。