这是我参与「第五届青训营」伴学笔记创作活动的第 3 天
Lecture3.1 JavaScript 编码原则之各司其责
Lead in: 如何写好JS
- 各司其职:HTML、CSS和JS职能分离
- 组件封装:可扩展可复用
- 过程抽象:函数式编程思想
从例子分析:设计支持浅色/深色模式的网页
- 简单粗暴:监听按钮文本,修改style(JS控制样式CSS的内容,代码不直观)
- 类名为主:根据类名判断,修改类名(更加简洁,代码更直观)
- 纯CSS:利用checkbox的
checked状态,利用兄弟节点选择器进行修改
<input id="modeCheckBox" type="checkbox">
<div class="content">
<label for="modeCheckBox" id="modeBtn" />
...
</div>
其中input元素设置display: none,由label进行控制
小结
- HTML控制结构,CSS控制展示,JS控制行为,各司其职
- 应当避免不必要的JS直接操作样式
- 可以用class表示状态
- 纯展示类交互可以寻求零JS方案
Lecture3.2 JavaScript 编码原则之组件封装
好的组件的特点
- 封装性
- 正确性
- 扩展性
- 复用性
从例子分析:轮播图组件
- HTML:使用无序列表作为结构
- CSS:绝对定位令图片重叠,使用
transition和``opacity`实现切换的动画 - JS
- api包含获取当前内容、当前下标、切换到某一张、切换到上一张、切换到下一张
- 可以定义一个类
- 构造函数:从HTML获取盒子和其中元素
- 获取当前内容:类名匹配
- 获取当前下标:数组操作
- 切换:修改当前内容和目标内容的类名
- 下一页:获取下标,切换到
idx+1 - 上一页:获取下标,切换到
idx-1
- 行为:控制流(四个小圆点和上页下页按钮)
- 使用自定义事件解耦
- 监听slide自定义事件,根据
idx设置切换器状态 - 切换的时候定义一个自定义事件
重构:插件化
- 控制元素(按钮和圆点)抽取成插件
- 插件和组件之间通过依赖注入建立联系
- 先注册插件,再启动轮播图
- 注册插件的地方进行事件绑定等操作
registerPlugins(...plugins) {
plugins.forEach(plugin => plugin(this));
}
传入的plugin其实都是方法
重构:模板化
- 通过类的render方法,渲染图片
render() {
const images = this.options.images;
const content = images.map(image => `
<li class="slider-list_item">
<img src="${image}" />
</li>
`.trim();
return `<ul>${content.join('')}</ul>`
- 插件渲染的时候,令innerHTML为其具体的HTML渲染
registerPlugins(...plugins) {
plugins.forEach(plugin => {
const pluginContainer = document.createElement('div');
pluginCOntainer.className = '.slider-list_plugin';
pluginContainer.innerHTML = plugin.render(this.options.images);
this.container.appendChild(pluginContainer);
plugin.action(this);
});
}
render方法渲染HTML,action方法控制其功能
组件框架
- 将组件通用模型抽象出来
- 渲染作为抽象方法出现各自实现
小结
- 组件设计原则:封装性、正确性、扩展性、复用性
- 实现组件的步骤:结构设计、展现效果、行为设计
- 三次重构:插件化、模板化、抽象化
Lecture3.3 JavaScript 编码原则之过程抽象
- 用于处理局部细节控制的一些方法
- 函数式编程思想的基础应用
短期内控制只执行一次的方法
function once(fn) {
return function(...args) {
if (fn) {
const ret = fn.apply(this, args);
fn = null;
return ret;
}
}
}
传入参数fn是函数
再次进入的时候,fn = null导致不会执行
高阶函数HOF
- 函数作为参数
- 函数作为返回值
- 常用于函数装饰器
常用高阶函数
- once:短时间内忽略重复
- throttle:节流函数,一段时间内只能执行一次
function throttle(fn, time = 500) {
let timer;
return function(...args) {
if (timer == null) {
fn.apply(this, args);
timer = setTimeout(() => {
timer = null;
}, time);
}
}
}
- debounce:防抖函数,用户操作停止后再触发
function debounce(fn, dur) {
dur = dur || 100;
var timer;
return function() {
clearTimeout(timer);
timer = setTimeout(() => {
fn.apply(this, arguments);
}, dur);
}
}
- consumer:异步转同步
- iterative:操作多个元素
纯函数
- 结果是可预期的
- 便于测试,用
console.assert(),不需要特定的外部环境 - 改变状态的函数不是纯函数
- 高阶函数的输出是确定的,可以用不同的函数作为参数测试
编程范式
- 主要有命令式和声明式
- JS全部都支持
- 命令式:关注怎么做
- 声明式:关注做什么
- 声明式可以省下逻辑分支的代价
Lecture3.4 Leftpad 事故背景引入
- 写代码还是要关注使用场景
Leftpad模块
- 功能:给数字前面补零
- 其实很简单,但是下架之后给很多npm包产生了影响
- 但也有很多可以优化的地方
- 利用repeat可以更高效地补充元素
- 但是使用场景下,优化微乎其微
Lecture3.5 JavaScript 代码质量优化之路
例1 交通灯
需求
- 若干个交通灯
- 依次改变颜色
如何优化?
- 简单粗暴:五个定时器
- 数据抽象
- 定义状态列表
- 递归调用切换函数
- 过程抽象
- 定义等待方法
- 定义轮换方法(参数是切换函数)
- 定义切换方法
- 轮换函数可以复用
- 异步+函数式
- 定义等待方法
- 定义切换方法
- 放进一个循环
例2 判断整数是不是4的幂
需求
- 如果是,返回
true - 如果不是,返回
false
如何优化?
- 简单粗暴:直接去除
- 按位操作:最后两位是不是11
- 数学方法
- 大于等于1
- 利用
a & a-1可以使得二进制数中的1减少一个(可以用数学归纳法证明),判断整个数的二进制只有一个1(在最高位) - 二进制数偶数位不能有1
- 字符串:先二进制,转字符串,再用正则表达式
例3 洗牌
需求
- 传入一个数组
- 将数组中的元素打乱
如何优化?
- 错误写法
- 根据随机数,如果大于0.5就交换
- sort方法的特性使然
- 数值越小,越可能出现在前面
- 随机抽一张牌,放到最后,扫描整个数组
- 使用生成器:用
yield可以取出前几张
例4 红包分配
需求
- 总金额已知
- 每人最少一分钱
如何优化
- 切西瓜法:每次都把最大的那部分分成两份,比较均匀
- 抽牌法:一个数列,随机插入“人数减一”个分隔符
小结
- 前端也需要算法
- 关注使用场景