青训营月影老师告诉我写好JavaScript的四大技巧——封装函数
Posted YK菌
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了青训营月影老师告诉我写好JavaScript的四大技巧——封装函数相关的知识,希望对你有一定的参考价值。
如何写好javascript是每一个前端工程师一直以来在思考的问题,月影老师告诉我们一些写好JavaScript的原则,同时也教了一些我们如何写好JavaScript的技巧,今天来继续跟着月影老师学JavaScript吧~~
起步
今天我们来讨论函数封装,我们要把JavaScript
代码写好,那一定要考虑代码的封装性,我们通过一个案例来学习学习函数封装的思想。
来看一个状态切换交通灯案例
案例:异步状态切换(交通灯)
这个例子的具体需求是,模拟交通灯信号,每隔一段时间,显示不同的颜色,循环切换状态
未封装:菜鸟版
这个拿给我们新手菜鸟,可能会这样写:
const traffic = document.getElementById('traffic');
(function reset(){
traffic.className = 's1';
setTimeout(function(){
traffic.className = 's2';
setTimeout(function(){
traffic.className = 's3';
setTimeout(function(){
traffic.className = 's4';
setTimeout(function(){
traffic.className = 's5';
setTimeout(reset, 1000)
}, 1000)
}, 1000)
}, 1000)
}, 1000);
})();
上面的这段菜鸟版代码虽然实现了我们的需求,但是它在设计上有很大的缺陷
第一个缺陷:reset
函数访问了外部环境traffic
,而它在函数内部不具有意义
这么做有两个问题:
- 如果我们修改了html代码,元素不叫做
traffic
了,这个函数就不工作了。 - 如果我们想把这个函数复用到其他地方,我们还得在那个地方重建这个
traffic
对象。
第二个缺陷是 回调地狱,我们要手写那么多次回调套回调,如果要增加或者减少状态,会很麻烦
出现这些问题的原因就是我们没有做到对函数进行封装!!!
所以,我们要封装函数,不能直接将traffic
这个对象直接写在函数中,也不能将状态切换的具体数据直接写在函数中
封装数据:数据抽象版
我们先对数据进行抽象,或者可以说将数据从函数中解耦出来~
首先,我们将traffic
变量作为函数的参数传入我们的start
函数中,这样函数体内部就没有完全来自外部环境的变量了
然后我们将状态(数据)抽象出来,形成一个对象数组,存着状态的名称state
和等待时间last
。将数组传入我们的start
函数中,递归调用applyState
函数,来实现状态切换。
这样做对函数进行了封装,将数据抽象出来,我们在修改数据的时候,不用修改函数体中的内容,使得我们函数的封装性得到了很大的提升。
const traffic = document.getElementById('traffic');
const stateList = [
{state: 'wait', last: 1000},
{state: 'stop', last: 3000},
{state: 'pass', last: 3000},
];
function start(traffic, stateList){
function applyState(stateIdx) {
const {state, last} = stateList[stateIdx];
traffic.className = state;
setTimeout(() => {
applyState((stateIdx + 1) % stateList.length);
}, last)
}
applyState(0);
}
start(traffic, stateList);
数据抽象就是把数据定义并聚合成能被过程处理的对象,交由特定的过程处理。 简单来说就是数据的结构化。
这里做出了两点改进
- 将外部变量变成参数传进函数
traffic
- 将状态数据与函数进行解耦,抽象数据
stateList
都提升了函数封装性和可复用性
封装行为:过程抽象版
在之前我们说到的三大原则的过程抽象 中,我们知道,不仅可以对数据进行抽象,也可以对过程进行抽象,下面我们来通过抽象过程来进行函数封装。
这次我们抽象出过程的两种操作:① 改变类名 ② 等待时间
① 改变类名封装成函数setState
function setState(state){
traffic.className = state;
}
② 等待时间 就是用wait函数封装setTimeout,我们把定时器的操作抽象出来,进行promise化,提高我们代码的可读性
function wait(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
将setTimeout函数封装成一个返回Promise的wait函数。
然后配合async/await语法,可以用同步代码风格写异步代码
这段代码与之前的代码相比,它的可读性是不是提高了很多?并且我们把他的过程都抽象出来了
const traffic = document.getElementById('traffic');
function wait(time){
return new Promise(resolve => setTimeout(resolve, time));
}
function setState(state){
traffic.className = state;
}
async function start(){
//noprotect
while(1){
setState('wait');
await wait(1000);
setState('stop');
await wait(1000);
setState('pass');
await wait(1000);
}
}
start();
封装行为:宏观版(封装轮询操作)
这里我们将改变类名和等待时间的操作封装在一起到setstate
中去,
将设置状态封装成函数,将这些状态作为轮询函数的参数
async function setState(state, ms){
traffic.className = state;
await wait(ms);
}
主要是要封装轮询函数
将循环播放抽象成一个轮询函数,用来切换状态
function poll(...fnList){
let stateIndex = 0;
return async function(...args){
let fn = fnList[stateIndex++ % fnList.length];
return await fn.apply(this, args);
}
}
就可以这样使用
let trafficStatePoll = poll(setState.bind(null, 'wait', 1000),
setState.bind(null, 'stop', 3000),
setState.bind(null, 'pass', 3000));
最终,完整的代码就是如下所示
const traffic = document.getElementById('traffic');
function wait(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
function poll(...fnList){
let stateIndex = 0;
return async function(...args){
let fn = fnList[stateIndex++ % fnList.length];
return await fn.apply(this, args);
}
}
async function setState(state, ms){
traffic.className = state;
await wait(ms);
}
let trafficStatePoll = poll(setState.bind(null, 'wait', 1000),
setState.bind(null, 'stop', 1000),
setState.bind(null, 'pass', 1000));
(async function() {
// noprotect
while(1) {
await trafficStatePoll();
}
}());
这样加强了我们函数的灵活性,我们状态切换的具体内容可以更改,而且将状态抽象出来,改变的状态的数量也很方便修改,直接在trafficStatePoll
中添加即可
总结
- 函数要做好封装,降低函数耦合性
- 要确保函数尽量不要直接使用和修改外部的变量,要用到外部变量,应该使其成为参数传入函数中
- 函数是一个处理数据的最小单元。它包含数据和处理过程
- 做好数据抽象,将用到的数据抽象出去形成对象或数组,可以提高函数的复用性
- 做好过程抽象,将过程进行抽象形成独立的函数,可以提高函数的复用性、灵活性
- 将异步操作进行
promise
化,可以提高函数的可读性
更多相关博文
【青训营】月影老师告诉我写好JavaScript的三大原则——各司其责
【青训营】月影老师告诉我写好JavaScript的三大原则——组件封装
【青训营】月影老师告诉我写好JavaScript的三大原则——过程抽象
【青训营】月影老师告诉我写好JavaScript的四大技巧——风格优先
【青训营】月影老师告诉我写好JavaScript的四大技巧——保证正确
也可以关注专栏:
【青训营笔记专栏】
以上是关于青训营月影老师告诉我写好JavaScript的四大技巧——封装函数的主要内容,如果未能解决你的问题,请参考以下文章
青训营月影老师告诉我写好JavaScript的四大技巧——风格优先
青训营月影老师告诉我写好JavaScript的四大技巧——风格优先
青训营月影老师告诉我写好JavaScript的四大技巧——妙用特性
青训营月影老师告诉我写好JavaScript的三大原则之——过程抽象