🌒

Hi Folks.

惰性调用

业务场景中,有些地方非常适合函数的惰性调用。利用惰性调用来优雅的实现代码的解耦合,同时也能快速的完成业务,准备下班。


介绍

所谓惰性调用也可以说是分步函数或者分段函数,从字面意思可以看出就是将一个功能点分两个或多个步骤完成。举个简单的分段函数例子:

1
const foo = a => b => a + b 

从这个例子能看出来,所谓分段函数就是我们通俗的讲的闭包,所谓闭包简单的讲就是函数内返回一个函数或者调用一个内部函数,在这个闭包作用域内会将变量的值锁住,即闭包内层函数可以获取到闭包外层函数的局部变量。这个理论是我根据自己的理解瞎讲的,自认为有点说法。

实践一下

1
2
3
4
const foo = a => => a + b

const bar = foo(1)
console.log(bar(1)) // 3(因为1+1=3)

可以看到,这个分段函数被分成了两步执行,执行第二步的时候也能获取到第一段传进来的值。这个就是简单的分段函数。


应用场景

扯了那么多没用,实践大于理论。来个demo看看。

image-20240413211842629

比如上面这个例子,点击列表项的选择按钮,在弹出模态框中选择合适项填充到列表项中。像这样的例子在erp系统中是非常常见的,如何使用函数惰性调用的方式来优雅的完成业务?看接下来的操作:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
<template>
<div class="list">
<div class="item" v-for="(item, index) in arrs" :key="index">
<span>{{ index + 1 }}</span>
<span class="name">{{ item.name || 'unset' }}</span>
<span class="cat">{{ item.category || 'unset' }}</span>
<span class="xxx">{{ item.xxx || 'unset' }}</span>
<!-- ...... -->
<button @click="showModal(item)">choose</button>
</div>
</div>

<XXXSelectModal ref="modalRef" @onGet="handleGet"/>
</template>
<script setup>
const arrs /* 列表项 */ = ref([......])
let callback = null
const modalRef = ref(null)
const showModal = item => {
// 定义第二段函数(仅定义,不执行)
callback = target => {
// 用选中的值覆盖列表项
// 在此作用域中能够拿到item的值
Object.keys(item).forEach(key => {
if (!target[key]) return
item[key] = target[key]
})
}

// 打开模态框
modalRef.value.show()
}

const handleGet /* 拿到选中值 */ = (slectedValue) => {
// 执行第二段函数,传递第二个参数
callback && callback(slectedValue)
callback = null
}
</script>

可以看到通过函数的分段执行,可以很轻松的实现业务的调用,这只是一个最简单的例子,在实际业务中往往会有更复杂更抽象的业务。掌握这个简单的操作可以省心很多。


🔺难度UP

「作为一个喜欢装逼的bugger,多少得写点让人觉得很屌的东西来展示下自己。」

函数分段执行也就是控制一个复杂函数的粒度,对于复杂函数的分段惰性操作,我们可以称之为函数柯理化。来点模拟业务处理的demo:

业务1:从这个数据中提取出所有name和age集合

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 数据
const data = [{
name: 'Bent',
age: 18
}, {
name: 'Jack',
age: 22
}, {
name: 'Mike',
age: 33
}]

// 实现定义好提取属性值的柯里化函数
const getField = field => obj => obj[field]

const nameArr = data.map(getField('name'))
const ageArr = data.map(getField('age'))

当然你可以直接…

1
2
const ageArr = data.map(el => el.age)
const nameArr = data.map(el => el.name)

业务2:根据不用场景需求来给对象数组按照字段排序

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
// 数据
const data = [{
name: 'Bent',
age: 18,
balance: 2284
}, {
name: 'Jack',
age: 22,
balance: 1232
}, {
name: 'Mike',
age: 33,
balance: 4523
}]


// 提前定义好柯里化函数
const execSort = (field, mode = 'asc') => {
return (a, b) => {
if (mode == 'desc') return b[field] - a[field]
if (mode == 'asc') return a[field] - b[field]
return 0
}
}

// 按照年龄升序
data.sort(execSort('age'))
// 按照年龄降序
data.sort(execSort('age', 'desc'))
// 按照.....

当然你可以直接…

1
2
3
4
5
6
7
// 按照年龄降序
data.sort((a, b) => b.age - a.age)
// 按照年龄升序
data.sort((a, b) => a.age - b.age)
// 按照余额降序
data.sort((a, b) => b.balance - a.balance)
// 按照.....

业务3:在每次网络请求前开启加载效果,请求结束后关闭加载效果。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// src/utils/loading.js
// 提前定义好工具函数并且导出
export const withLoading = (fn) => {
return (...args) => {
try {
showLoading()
const res = fn(...args)
if (res instanceof Promise) {
return res.then(resFix => {
hideLoading()
return resFix
}).catch(e => {
hideLoading()
throw e
})
}
} catch (e) {
hideLoading()
throw e
}
}
}

在业务中引用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import { withLoading } from '@/utils/loading'

// 伴随加载
const getDataWithLoading = async () => {
const res = await withLoading($axios.blog.getBlogById)(1)
}

// 不伴随加载
const getData = async () => {
const res = await $axios.blog.getBlogById(1)
}

// 我可以自由的控制一个异步函数是否要伴随加载效果
getData()
getDataWithLoading()

这时候你就会发现,用常规的方式没办法做到这么优雅的解耦,传统的方式你通常会这样实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
const getDataWithLoading = async () => {
try {
showLoading()
const res = await $axios.blog.getBlogById(1)
hideLoading()
return res.data
} catch (e) {
hideLoading()
throw e
}
}

getData()
// 糟糕~每次我想要加载效果都要写这么一坨!

这是函数柯理化的作用就体现出来了,withLoading这个函数是一个纯函数,主要的作用就是给异步操作提供加载效果,它不会对作用域造成污染。还不赶紧用起来?


文末;

函数的惰性调用能够让你和其他函数进行很好的组合工作,但是最好做到每个函数的单一职责,避免对作用域造成污染或者减少作用域污染。本文主要提供FP(函数式编程)的思路,让您在遇到问题的同时有一些巧妙的解决办法……如果你对函数式编程比较感兴趣的话,可以看看我之前写的文章。「文章:函数式编程」在这篇文章里你会看到一些更有意思的东西。


👋 ;

, — 2024年4月13日