JavaScript 编码原则
写好 JS 的一些原则:
-
各司其责
让 HTML、CSS 和 JavaScript 职能分离。
-
组件封装
好的 UI 组件具有正确性、扩展性、复用性。
-
过程抽象
应用函数式编程思想。
各司其责
如果需要通过 JS 来实现样式的修改,不要在 JS 中通过 style 修改样式,而是将样式定义在 CSS 中,通过修改类名来修改样式。
例如:
// 通过 JS 修改样式
const body = document.body;
if(e.target.innerHTML === 'white') {
body.style.backgroundColor = 'black';
body.style.color = 'white';
e.target.innerHTML = 'black';
} else {
body.style.backgroundColor = 'white';
body.style.color = 'black';
e.target.innerHTML = 'white';
}
// 通过 JS 修改类名
const body = document.body;
if(body.className === 'white') {
body.className = 'black';
} else {
body.className = 'white';
}
更优解——纯 CSS 实现(如果只修改样式,而没有其他逻辑)
<!-- 将原来的 button 改为以下格式 -->
<input id="modeCheckBox" type="checkbox">
<!-- 将原来放 button 的位置替换为 label -->
<!-- 通过 label 的 for 属性将 label 指定为 id 为 modeCheckBox 的 input,即:点击 label 的效果和点击 input 的效果一致 -->
<label id="modeBtn" for="modeCheckBox">
/*
css 中的样式
*/
#modeCheckBox {
/* label 实现了原来 button 的效果,将 input 隐藏起来更美观 */
display: none;
}
/* 当 input 为选中时,其类名为 content 的兄弟标签改变样式,自身的样式也会更改,但是不需要关注 */
#modeCheckBox:checked + .content {
background-color: black;
color: white;
}
/* 不需要初始化 .content 的样式,因为默认为白底黑字 */
总结
- HTML / CSS / JS 各司其责
- 应当避免不必要的由 JS 直接操作样式
- 可以用 class 来表示状态
- 纯展示类交互寻求零 JS 方案
组件封装
组件是指 Web 页面上抽出来一个个包含模板(HTML)、样式(CSS)和功能(JS)的单元。
好的组件具备封装性、正确性、扩展性、复用性。
例如:一个轮播图
-
结构(HTML)
轮播图是一个典型的列表结构,可以使用无序列表 ul 元素来实现。
-
表现(CSS)
- 使用 CSS 绝u第定位将图片重叠在同一个位置
- 轮播图切换的状态使用修饰符(modifier)
- 轮播图的切换动画使用 CSS transition
-
行为(JS)
Slider
- getSelectedItem()
- getSelectedItemIndex()
- slideTo()
- slideNext()
- slidePrevious()
控制流
- 使用自定义事件来解耦
重构
插件化
- 将控制元素抽取成插件
- 插件与组件之间通过依赖注入方式建立联系
模板化
- 将 HTML 模板化,更易于扩展
抽象化
- 将组件通用模型抽象出来
总结
- 组件设计的原则:封装性、正确性、扩展性、复用性
- 实现组件的步骤:结构设计、展现效果、行为设计
- 三次重构:插件化、模板化、抽象化(组件框架)
过程抽象
- 用来处理局部细节控制的一些方法
- 函数式编程思想的基础应用
高阶函数
概念
- 以函数作为参数
- 以函数作为返回值
- 常用于作为函数装饰器
常用高阶函数
- Once
- Throttle
- Debounce
- Consumer
- Itrative
once
为了能够让 “只执行一次” 的需求覆盖不同的事件处理,可以将这个需求剥离出来。这个过程称为过程抽象。
// DOM 事件只触发一次的方式
// 方式一:设置 options 配置项
btn.addEventListener('click', ()=>{}, {
once: true
})
// 方式二:将回调函数配置为高阶函数
function once(fn) {
return function(...args) {
if(fn) {
const res = fn.apply(this, args);
fn = null;
return res;
}
}
}
btn.addEventListener('click', once(()=>{}))
consumer
每隔一段时间调用一次指定的函数。
function consumer(fn, time) {
let tasks = [], timer;
return function(...args) {
tasks.push(fn.bind(this, ...args));
if(!timer) {
timer = setInterval(() => {
tasks.shift[].call(this);
if(tasks.length <= 0) {
clearInterval(timer);
timer = null;
}
}, time);
}
}
}
iterative
批量操作函数。
function iterative(fn) {
const isIterable = obj => {
return obj && typeof obj[Symbol.iterator] === 'function';
}
return function(subject, ...rest) {
if(isIterable(subject)) {
const res = [];
for(let obj of subject) {
res.push(fn.apply(this, [obj, ...rest]));
}
return res;
}
return fn.apply(this, [subject, ...rest]);
}
}
为什么要使用高阶函数?
-
纯函数:在任何时候都可以预期结果的函数。
容易测试。
-
非纯函数:不能预期结果的函数。
难以测试,项目越多越难维护。
高阶函数可以降低非纯函数的使用度,利于项目维护。
编程范式
命令式与声明式
-
命令式:强调怎么做
简单,不利于维护。
// 例子:点击按钮切换状态 btn.onclick = function(el) { if(el.target.className === 'on') { el.target.className = 'off'; } else { el.target.className = 'on'; } } -
声明式:强调做什么
复杂,利于维护。
// 例子:点击按钮切换状态 function toggle(...actions) { return function(...args) { let action = actions.shift(); actions.push(aciton); return action.apply(this, args); } } btn.onclick = toggle( el => el.target.className = 'off', el => el.target.className = 'on' )
代码质量优化
- 避免回调地狱,采用异步实现更加清晰
- 在特点情况下,用生成器函数替换普通函数
总结
- 能用 HTML、CSS 实现的功能不要用 JS
- 代码尽量不要为了简单而写,要尽量为了复用性、扩展性而写
- 一个项目如果可以的化尽量拆分成多个小项目
- 代码要注重时、空复杂度