JS成功学之「过程抽象」 | 青训营笔记
这是我参与「第四届青训营 」笔记创作活动的的第4天。
写好JS的三大原则:
- 各司其责: 组件封装过程抽象让HTML、CSS和JavaScript职能分离。
- 组件封装: 好的UI组件具备正确性、扩展性、复用性。
- 过程抽象: 应用函数式编程思想。
接下来将详细探讨一下 「过程抽象」 这一原则。
过程抽象
- 所谓过程抽象,其实是一种思维方式,也是一种编程方式,我们可以把函数当成一个控制器,控制这函数的输入和输出,它也是函数式编程思想的基础。
示例
如有以下一个列表,当任务完成,勾选之后该任务就消失,消失的时候有一个动画效果
可能有的人会这样实现
- 先得到列表list
- 监听每个按钮的点击事件
- 加一个定时,2s之后移除完成的
- 通过改变class实现一个过渡的动画效果
const list = document.querySelector('ul');
const buttons = list.querySelectorAll('button');
buttons.forEach((button) => {
button.addEventListener('click', (evt) => {
const target = evt.target;
target.parentNode.className = 'completed';
setTimeout(() => {
list.removeChild(target.parentNode);
}, 2000);
});
});
此版本存在的问题
- 如果连续多次点击复选框,复选框已经被移除,后面会报Dom节点不存在的错误
原因
- 多次点击,就会执行多个setTimeout,当同一的DOM被移除一次之后,之后的remove就找不到了这个DOM了
改进
- 应该只触发一次click事件
扩展
- 一些异步交互
- 一次性的HTTP请求
- 提交表单,或者其他一些任务
都应该限制操作次数
Once
为了能够让“只执行一次“的需求覆盖不同的事件处理,我们可以将这个需求剥离出来。这个过程我们称为过程抽象。
function once(fn) {
return function(...args) {
if(fn) {
const ret = fn.apply(this, args);
fn = null;
return ret;
}
}
}
const list = document.querySelector('ul');
const buttons = list.querySelectorAll('button');
buttons.forEach((button) => {
button.addEventListener('click', once((evt) => {
const target = evt.target;
target.parentNode.className = 'completed';
setTimeout(() => {
list.removeChild(target.parentNode);
}, 2000);
}));
});
这里给函数传入function,第一次进来首先会为true,if中的内容执行,在执行之后立马function被设为null,下次进入这个函数的时候,if就不会执行了,因为null在条件判断中相当于false。
高阶函数
- 高阶函数一般以函数作为参数并且把函数作为返回值。常用于作为函数装饰器。上面的例子我们也可以在函数处理前后添加其他操作,比如在发送数据的时候添加请求头等等。
function HOF0(fn) {
return function(...args) {
return fn.apply(this, args);
}
}
常用高阶函数
- Once
- Throttle(节流函数)code.h5jun.com/gale/1/edit…
使用场景:控制事件触发频率,比如不论多快的点击按钮,都是隔500毫秒才执行一次事件
- Debounce(防抖函数 )code.h5jun.com/wik/edit?js…
使用场景:也是用来限制事件的调用、但是它不仅是限制调用的频率,如用户在频繁的编辑一段文字,这段文字会自动进行保存,如果用户很快的修改,而我们一直在提交,对服务器压力是很大的,这个时候我们可以等到用户编辑停止几百毫秒之后再执行提交操作
- Consumer (异步消耗)code.h5jun.com/roka/7/edit… code.h5jun.com/bucu/3/edit…
- iterative(迭代函数)code.h5jun.com/kapef/edit?…
为什么要用高阶函数?
函数可以分为
- 纯函数
- 非纯函数:
一个项目、系统或库,理论上来说纯函数越多,可维护性越好。(纯函数也便于编写单元测试)
纯函数
当一个function,输入的值确定了,输出的值是唯一确定的,那么这样的一个函数就是纯函数。
纯函数的两个原则
- 这个函数无副作用
- 具有幂等性,在任何时间、次序下去调用它,它的结果是唯一的,不会被改变的
举个栗子:
// 纯函数
function add(a,b){
return a + b;
};
// 非纯函数
let num = 6;
function add1(){
return num++;
}
console.log(add(1,2));//3
console.log(add1());//6
console.log(add1());//7
编程范式
命令式与声明式
有的语言是纯命令式,有的是纯声明式,但是JavaScript都有!
如以下,要实现数组元素*2操作,既可以用命令式又可以用声明式
//命令式
let list = [1, 2, 3, 4];
let mapl = [];
for(let i = 0; i < list.length; i++) {
mapl.push(list[i] * 2);
}
//声明式
let list = [1, 2, 3, 4];
const double = x => x * 2;
list.map(double);
示例
-
命令式(强调的是我们要怎么做,就是How)
-
声明式(强调的是我们要做什么,就是What,可扩展性更强)
实现一个开关
- 命令式写法
switcher.onclick = function(evt){
if(evt.target.className === 'on'){
evt.target.className = 'off';
}else{
evt.target.className = 'on';
}
}
- 声明式写法
function toggle(...actions){
return function(...args){
let action = actions.shift();
actions.push(action);
return action.apply(this, args);
}
}
switcher.onclick = toggle(
evt => evt.target.className = 'off',
evt => evt.target.className = 'on'
);
当只有两种状态时,似乎没有区别,但是如果开关有【开】【关】【警告】三种情况之后呢?
命令式需要多加一个if判断,但是声明式只需要多传一个参数。
function toggle(...actions){
return function(...args){
let action = actions.shift();
actions.push(action);
return action.apply(this, args);
}
}
switcher.onclick = toggle(
evt => evt.target.className = 'warn',
evt => evt.target.className = 'off',
evt => evt.target.className = 'on'
);
参考博客:
- 链接:juejin.cn/post/699850…,作者:Axjy
- 链接:juejin.cn/post/700780…,作者:大熊G