JS学习记录 | 青训营
这是我参与「第五届青训营 」伴学笔记创作活动的第 3 天
如何写好JavaScript
遵循一些原则:
- 各司其职 -------------->HTML,CSS和JavaScript职能分离
- 组件封装 -------------->UI组件的正确性、拓展性、复用性
- 过程抽象 -------------->函数式编程思想
各司其职
应当避免由JS直接操作样式
当只有纯展示类交互时,应当避免用js来直接操作样式。
使用HTML和CSS的伪类有时候也可以达到相同的效果。
当使用JS进行直接操作样式:
如切换白天或者夜间模式,可以使用JS控制dom API达到。这种方式相当于是用JS控制CSS。
如果项目换个人维护,未必就明白这段代码是做什么的。
操作HTML状态,符合各司其职
使用不同的class进行切换。这种方式很容易理解。
采用不同的className能够直观的展示这段代码的作用。
使用HTML或者CSS进行控制
如:白天和夜间模式切换
使用CSS伪类选择器和HTML标签
给label标签加一个for属性,内容是一个checkbox的id,
将checkbox设置为display:none。隐藏这个checkbox。
使用伪类和选择器进行改变样式
组件封装
当一个组件由HTML,CSS,JS设计出来的时,如果后续要修改这个组件显示在网页上的情况,可能需要修改HTNL,CSS,JS的代码。当产品需求不断改变时,重复性的修改这些代码容易出错。所以需要将组件中的内容插件化。最后将插件和组件之间通过依赖注入的形式表现出来。
简单描述:将原来的代码进行重构,对内容进行解耦。将控制元素抽取成插件。将插件与组件之间通过依赖注入的方式建立联系。
将HTNL模板化
以轮播图为例,当需要修改显示的图片的时候,不应该去修改HTNL代码,而是通过修改数据从而使得轮播图上的图片被修改。这时就需要将HTML模板化,根据数据渲染出想要的效果。
使用到render()方法,通过将数据传到render从而渲染出对应的组件,此时引用的HTML代码只需要一行。
重构后的代码能够很方便的修改需要使用的显示内容,也能够方便添加新的功能。
抽象
将组件用通用模型抽象出来,抽象出简单的组件框架。
后续还能够对CSS进行模板化,这样就能够使得只修改数据就能达到效果。
过程抽象
是函数式编程的基本应用。
为了能让“只执行一次”的需求覆盖不同的事件处理,可以将需求剥离出来,这个过程叫做过程抽象。
用高阶函数完成过程抽象
HOF
以函数作为参数,用函数作为返回值,常用于函数装饰器
等价高阶函数 ------------>接收的参数函数和返回的参数函数是一致的
- Once()
当需要点击请求只能请求一次时,可以添加{once: true}参数
如:
function once(fn) {
return function(...args) {
if (fn) {
const ret = fn.apply(this, args);
fn = null;
return ret;
}
}
}
const list = document.qwerySelector('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);
}, {
once: true
});
})
只请求一次数据库,使用once()将过程封装。
const list = document.qwerySelector('ul');
const buttons = list.querySelectorAll('button');
buttons.forEach((button) => {
button.addEventListener('click', once((evt) => {
const target = evt.target;
target.parentNode.className = "completed";
setTimeout(
() => {
list.renoveChild(target.parentNode);
}, 2000
);
})
)})
这样只会对数据库进行一次请求。 在once中,第一次调用if(fn)后,fn会被清空成null,那么之后就不会被调用if。从而保证了只调用一次。
- Throttle
节流函数,鼠标移动或者滚动,触发频率高,会带来一定的性能开销。可以通过该函数设定频率。
function throtttel(fn, time = 500){
let timer;
return function(...args){
if(timer == null){
fn.apply(this, args);
timer = setTimeout(() => {
timer = null;
},time)
}
}
}
只有在超时的时候才会继续调用。
- dbounce
防抖函数
理解:例如:自动保存,在编辑过程中不自动保存,在编辑完成后若干毫秒开启自动保存。
function debounce(fn, dur){
dur = dur || 300;
var timer;
return function(){
clearTimeout(timer);
timer = setTimeout(() => {
fn.apply(this, arguments);
},dur);
}
}
当调用该函数时,一直清空timer的值,当不再调用函数的时候,调用fn.apply()
- Comsumer/2
"累计" 例如:鼠标点击事件,当不断点击时,记录点击次数,然后每隔若干秒进行一次累加。最后耗尽点击次数为止。
function consumer(fn, time) {
let tasks = [],
timer;
return function(...args) {
tasks.push(fn.bind(this, ...args));
if (timer == null) {
timer = setInterval (
() => {
tasks.shift().call(this)
if (tasks.length <= 0) {
clearInterval(timer);
timer = null;
}
}, time
)
}
}
}
- Iterative
const isIterable = obj => obj != null
&& typeof obj[Symbol.iterator] === 'function';
function iterative(fn) {
return function(subject, ...rest) {
if (isIterable(subject)) {
const ret = [];
for (let obj of subject) {
ret.push(fn.apply(this, [obj, ...rest]))
}
}
return ret;
}
return fn.apply(this, [subject, ...rest]);
}
判断subject是否是可迭代元素,用每个元素调用fn方法。
测试正确性
使用一个加法就能够测试它的正确性
function add(x, y){
return x + y
}
const addMany = iterative(add);
console.log(addMany([1, 2, 3, 4], 5)); //得到6,7,8,9
纯函数
无状态的函数,任何a输入都达到b 没有副作用,可预期
function add(x, y){
return x + y;
}
非纯函数
let idx = 0;
function count(){
return idx ++;
}
纯函数的好处是任何时候都能进行测试,不需要配置测试环境就能得到想要的值。
非纯函数测试时需要建立特定的外部环境。
非纯函数越多,可维护性越差
可以使用iterative高阶函数将非纯函数转化为纯函数。
const xxx = iterative(xxxx函数)
编程范式
命令式与声明式
JavaScript两者都有。
命令式:面向过程,面向对象
更强调怎么做。
let list = [2, 3, 4, 5];
let chg = []
for(let i = 0;i < list.length;i ++){
chg.push(list[i] * 2);
}
声明式: 逻辑思考函数式
let list = [2, 3, 4, 5];
const double = x => x * 2;
list.map(double);
声明式有时候可能需要写一个新函数,没有命令式来的方便。
当业务逻辑拓展的时候,采用命令式也许会大大增加代码行数。使得代码过于冗长。而使用声明式只需要添加少数代码完成。
引用参考:
JavaScript 编码原则之各司其责 - 掘金 (juejin.cn)
JavaScript 编码原则之组件封装 - 掘金 (juejin.cn)
JavaScript 编码原则之过程抽象 - 掘金 (juejin.cn)