函数式编程
与其他编程模式区别
—与面向过程,面向对象编程并列
面向过程:按照步骤来实现我们想实现的功能
面向对象的思维方式:把现实社会中的事物抽象成程序世界中的类与对象,通过封装继承多态来演示事物的联系;
函数式编程:把现实社会中的事物抽象成程序世界中的运算过程
函数式开发库lodash;
let num1=1;
let num2=2;
let sum=num1+num2;
这是一个面向过程编程
function add(num1+num2){
return num1+num2
}
let sum=add(1,2)
这是一个函数式编程
函数式一等公民
函数可以储存在变量中
javascript中函数就是一个普通对象 new Function()
var fn=function(){
console.log("first class function")
}
var Views={index:function(a){console.log(a)}}
const blog ={index(posts){return Views.index(posts)}}
//等价于
const blog ={index:Views.index}
//我们是要把View.index这个方法 赋值到blog里面的index方法
//不是吧views.index调用,给另一个方法
//所以index后面加: 因为我们是要赋值方法不是赋值这个返回值 所以这个(posts)去掉
blog.index(18)//18
函数作为返回值
函数可以作为参数
高阶函数
函数作为参数
对通用问题的抽象,只关注实现问题的目标
forEach(arr,fn){
for(let i=0,i<arr.length;i++){
fn(arr[i])
}
}
forEach([1,2,3,4,5,6],function(item){
console.log(item)
})
//filter
function filter(arr,fn){
let resarr=[];
for(let i=0;i<arr.length;i++){
if(fn(arr[i])){
resarr.push(arr[i])
}
}
return resarr
}
let result=filter([1,2,3,4,5,6],function(item){
return item%2==0
})
函数作为返回值
function makeFn () {
let msg = 'Hello function'
return function () {
console.log(msg)
}
}
const fn = makeFn()
fn()
makeFn()()
//支付一次例子
function once(fn) {
let done = false
return function () {
if (!done) {
done = true
return fn.apply(this, arguments)
//将外面的fn赋值到这个返回的函数去 arguments [Arguments] { '0': 5 }
}
}
}
let pay = once(function (money) {
console.log(`支付: ${money} RMB`)
})
//once 函数作为返回值
pay(5)
pay(5)
pay(5)
pay(5)
常用的高阶函数
// 模拟常用高阶函数:map、every、some
// map
const map = (array, fn) => {
let results = []
//for of 是对for循环的抽象
for (let value of array) {
results.push(fn(value))
}
return results
}
// 测试
// let arr = [1, 2, 3, 4]
// arr = map(arr, v => v * v)
// console.log(arr)
// every
const every = (array, fn) => {
let result = true
for (let value of array) {
result = fn(value)
//如果有一个不满足的时候 就不继续执行
if (!result) {
break
}
}
return result
}
// 测试
// let arr = [9, 12, 14]
// let r = every(arr, v => v > 10)
// console.log(r)
闭包closure
闭包:函数和其周围的状态(词法环境)的引用捆绑在一起形成闭包;
可以在另一个作用域中调用一个函数的内部函数并访问到该函数的作用域中的成员;
本质:函数在执行的时候会放到一个执行栈上,当函数执行完毕之后会从执行栈上移除,但是堆上的作用域成员因为被外部引用不能释放,因此内部函数依然可以访问外部函数的成员在这里插入代码片
//例子
function makeSalary (base) {
return function (performance) {
return base + performance
}
}
let salaryLevel1 = makeSalary(12000)
let salaryLevel2 = makeSalary(15000)
console.log(salaryLevel1(2000))
console.log(salaryLevel2(3000))
纯函数
相同的输入始终有相同的输出;
就是数学的函数,
数组的slice和splice分别是:纯函数和不纯函数
slice 返回数组中的指定部分,不会改变原数组
splice 对数组进行操作返回该数组,会改变原数组
// 纯函数和不纯的函数
// slice / splice
let array = [1, 2, 3, 4, 5]
// 纯函数
console.log(array.slice(0, 3))
console.log(array.slice(0, 3))
console.log(array.slice(0, 3))
// 不纯的函数
console.log(array.splice(0, 3))
console.log(array.splice(0, 3))
console.log(array.splice(0, 3))
lodash
链接: 使用文档.
是一个纯函数的功能库,提供了对数组,数字,对象,字符串,函数等操作的一些方法
// 演示 lodash
// first / last / toUpper / reverse / each / includes / find / findIndex
const _ = require('lodash')
const array = ['jack', 'tom', 'lucy', 'kate']
//console.log(_.first(array)) //jack
//console.log(_.last(array))//kate
//console.log(_.toUpper(_.first(array)))//JACK
//console.log(_.reverse(array))//['kate','lucy','tom','jack']
//1.each----------------------------------------------------------
const r = _.each(array, (item, index) => {
console.log(item, index)//kate 0 lucy 1
})
console.log(r)
//2.curry----------------------------------------------------------
//柯里化 lodash 中的 curry 基本使用
const _ = require('lodash')
function getSum (a, b, c) {
return a + b + c
}
const curried = _.curry(getSum)
//console.log(curried(1, 2, 3))
//console.log(curried(1)(2, 3))
//console.log(curried(1, 2)(3))
案例
const _ = require('lodash')
const match = _.curry(function (reg, str) {
return str.match(reg)
})
//第一个参数 空格 数字匹配 数字
const haveSpace = match(/\s+/g)
const haveNumber = match(/\d+/g)
//第二个参数 是正则判断的对象
// console.log(haveSpace('helloworld'))
// console.log(haveNumber('abc'))
const filter = _.curry(function (func, array) {
return array.filter(func)
})
//第一个参数匹配有空格的数组对象
const findSpace = filter(haveSpace)
//console.log(filter(haveSpace, ['John Connor', 'John_Donne']))
//console.log(findSpace(['John Connor', 'John_Donne']))
//3.memoize--------------------------------------------------------
------------lodash 实现过程-------------------
// 1.记忆函数memoize 实现过程
const _ = require('lodash')
function getArea (r) {
console.log(r)
return Math.PI * r * r
}
// let getAreaWithMemory = _.memoize(getArea)
// console.log(getAreaWithMemory(4))
// console.log(getAreaWithMemory(4))
// console.log(getAreaWithMemory(4))
// 模拟 memoize 方法的实现
function memoize (f) {
let cache = {}
return function () {
let key = JSON.stringify(arguments)
cache[key] = cache[key] || f.apply(f, arguments)
return cache[key]
}
}
let getAreaWithMemory = memoize(getArea)
//console.log(getAreaWithMemory(4))
//console.log(getAreaWithMemory(4))
//console.log(getAreaWithMemory(4))
// 2.模拟实现 lodash 中的 curry 方法
function getSum (a, b, c) {
return a + b + c
}
const curried = curry(getSum)
//console.log(curried(1, 2, 3))
//console.log(curried(1)(2, 3))
//console.log(curried(1, 2)(3))
function curry (func) {
return function curriedFn(...args) {
// 判断实参和形参的个数
if (args.length < func.length) {
//args[ 1, 2 ] [Arguments] { '0': 1, '1': 2 }
return function () {
// [ 1, 2 ] [Arguments] { '0': 3 }
//arguments是伪数组 通过Array.from(arguments)转化成数组与之前的数组args合并
//[1,2].concat(Array.from({'0': 3 ,length:1}))(3) [1, 2, 3]
return curriedFn(...args.concat(Array.from(arguments)))
}
}
//curried(1, 2, 3) 直接返回
return func(...args)
}
}
//4.mapKeys------------------------------------------------
let data = _.mapKeys({ 'a': 1, 'b': 2 }, function(value, key) {
return key + value;
});
//data => { 'a1': 1, 'b2': 2 }
副作用
副作用让一个函数变得不纯
副作用来源:
1.配置文件
2.数据库
3.获取用户的输入
4.函数外的全局变量
函数依赖外部的状态就无法保证相同输出
柯里化
// 柯里化演示
function checkAge (age) {
let min = 18//硬编码
return age >= min
}
// 把硬编码的函数改造成纯函数
function checkAge (age,min) {
return age >= min
}
// console.log(checkAge(18, 20))
// console.log(checkAge(18, 24))
// console.log(checkAge(22, 24))
// 函数的柯里化
function checkAge (min) {
return function (age) {
return age >= min
}
}
let checkAge=min=>(age=>age>=min)//箭头函数没有括号{}的时候是返回
//调用checkAge(18)会返回function (age) { return age >= min}函数存在这个chekAge18变量里
let checkAge18=checkAge(18);
checkAge18(20)
定义:当一个函数有多个参数的时候,我们可以对函数进行改造,只传递一个参数,让这个函数返回一个新的函数,让这个新的函数去接收剩余的参数这就是柯里化
1.柯里化 可以让我们给一个函数传递较少的参数得到一个已经记住了某些固定参数的新函数
2.这是一种对参数的缓存
3.让函数变得更灵活,让函数的粒度更小
4.可以把多元函数转换成一元函数,可以组合使用函数产生强大的功能
柯里化
柯里化的使用
function fn (a, b, c) {
console.log(a + b + c)
}
let fn1 = currying(fn, 1)
let fn2 = fn1(2)
fn2(3) // 6
uncurrying
使用call,apply可以让非数组借用一些其他类型的函数,比如,Array.prototype.push.call, Array.prototype.slice.call, uncrrying把这些方法泛化出来,不在只单单的用于数组,更好的语义化。
反柯里化的实现
Function.prototype.uncurrying = function () {
let self = this
return function () {
let obj = Array.prototype.shift.call(arguments)
return self.apply(obj, arguments)
}
}
// push可以接受非数组的参数
let push = Array.prototype.push.uncurrying()
(function () {
push(arguments, 4)
})(1,2,3)
函数节流
规定在一个单位时间内,只能触发一次函数。如果这个单位时间内触发多次函数,只有一次生效。
方法一:
function throttle(fn, gapTime) {
if (gapTime == null || gapTime == undefined) {
gapTime = 1500
}
let _lastTime = null
// 返回新的函数
return function () {
let _nowTime = +new Date()
if (_nowTime - _lastTime > gapTime || !_lastTime) {
fn.apply(this, arguments) //将this和参数传给原函数
_lastTime = _nowTime
}
}
}
eg:
import util from "@/utils/util"
clickHandle: util.throttle(function() {
...
}, 2000),
方法二:
export function throttle(fn, wait) {
let previous = 0
return function (...args) {
const now = Date.now()
if ((now - previous) > wait) {
fn.apply(this, args)
previous = now
}
}
}
eg:
onceClick:throttle(function(){
...
},1000),
static throttle(func, delay){
var timer = null;
return function(){
var context = this;
var args = arguments;
if(!timer){
timer = setTimeout(function(){
func.apply(context, args);
timer = null;
},delay);
}
}
}
onceClick: throttle(function() {
....
}, 100),
函数防抖
在事件被触发 n秒后再执行回调,如果在n秒内又被执行,则重新计时。
加入了防抖以后,当你在频繁的输入时,并不会发送请求,只有当你在指定间隔内没有输入时,才会执行函数。用于input框居多
export const debounce = (func, wait, immediate) => {
let timeout
return function () {
let context = this
let args = arguments
if (timeout) {
clearTimeout(timeout)
}
if (immediate) {
let callNow = !timeout
timeout = setTimeout(() => {
timeout = null
}, wait)
if (callNow) {
typeof func === 'function' && func.apply(context, args)
}
} else {
timeout = setTimeout(() => {
typeof func === 'function' && func.apply(context, args)
}, wait)
}
}
}
eg:
onClick={debounce(() => handlefn(), 1000)}