Lecture3 「跟着月影学 JavaScript」 | 青训营笔记

111 阅读5分钟

这是我参与「第五届青训营」伴学笔记创作活动的第 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 红包分配

需求

  • 总金额已知
  • 每人最少一分钱

如何优化

  • 切西瓜法:每次都把最大的那部分分成两份,比较均匀
  • 抽牌法:一个数列,随机插入“人数减一”个分隔符

小结

  • 前端也需要算法
  • 关注使用场景