如何写 好 JavaScript | 青训营笔记

85 阅读4分钟

这是我参与「第四届青训营」笔记创作活动的第3天

如何写 JavaScript

本篇笔记内容分为“写好JS的原则”和“从不同方向优化代码”两个部分
第一部分:各司其责、组件封装、过程抽象。
第二部分:从哪些方面改进以适应不同的使用场景。例:交通灯的切换

写好JS的一些原则

各司其责:让HTML、CSS和JavaScript职能分离。

组件封装:好的UI组件具备正确性、扩展性、复用性。

过程抽象:应用函数式编程思想。

一、各司其责

写一段JS,控制一个网页,让它支持浅色和深色两种浏览模式。

版本一: code.juejin.cn/pen/7108183…

版本二:

code.juejin.cn/pen/7108183… 版本二问题:用JavaScript控制CSS,修改时不方便,不便理解代码含义。

版本三:

code.juejin.cn/pen/7108184…

重点:各司其责,改变样式由CSS实现。

通过这个案例,我们可以得到结论:

  • HTML/CSS/JS 应该各司其责
  • 应当避免不必要由JS直接操作的样式
  • 可以用class来表示状态
  • 纯展示类交互寻求零JS方案

2、组件封装

组件是指Web页面上抽出来一个个包含模板(HTML)、功能(JS)和样式(CSS)的单元。

好的组件具备封装性、正确性、扩展性、复用性。

例:用原生JS写一个电商网站的轮播图,应该怎么实现?

结构设计:HTML。轮播图是典型的列表结构,我们可以使用无序列表ul元素来实现。

表现效果:CSS。

  • 使用CSS绝对定位将图片重叠在同一个位置
  • 轮播图切换的状态使用修饰符(modifier)
  • 轮播图的切换动画使用CSS transition

行为设计:JS。

API(功能)

  • getSelectedItem()
  • getSelectedItemIndex()
  • slideTo()
  • slideNext()
  • slidePrevious()

Event(控制流)

  • 使用自定义事件来解耦

code.juejin.cn/pen/7108187…

问题:构造函数代码复杂;控制点与组件不灵活

改进:插件化

解耦

  • 将控制元素抽取成插件
  • 插件与组件之间通过依赖注入方式建立联系

code.juejin.cn/pen/7108191…

改进:模板化

解耦

  • 将HTML模板化,更易于扩展

code.juejin.cn/pen/7108191…

改进:组件框架

抽象

  • 将组件通用模型抽象出来

code.juejin.cn/pen/7108185…

总结:

  • 组件设计的原则:封装性、正确性、扩展性、复用性
  • 实现组件的步骤:结构设计、展现效果、行为设计
  • 三次重构:插件化、模板化、抽象化(组件框架)

思考:改进的空间:现在的设计是插件与组件分离,这个设计的好处是简单、扁平化的,但是它存在的问题是没有考虑父子组件,可以考虑把插件跟组件给融合在一起,子组件可以作为父主线的插件来使用。

2、目前只做了HTML的模板化,没有做CSS模板化。


过程抽象

1、用来处理局部细节控制的一些方法

2、函数式编程思想的基础应用

例:操作次数限制

  • 一些异步交互
  • 一次性的HTTP请求

高阶函数 Once

为了能够让“只执行一次”的需求覆盖不同的事件处理,我们可以将这个需求剥离出来。这个过程我们称为过程抽象

高阶函数 HOF

  • 以函数作为参数
  • 以函数作为返回值
  • 常用于作为函数装饰器

Once、Throttle(节流函数)、Debounce【防抖】(超过一定时间后自动保存)、Consumer/2(延时调用)、Iterative(批量操作)。

编程范式

命令式与声明式

命令式:

let list = [1, 2, 3, 4];
let mapl = [];
for(let i = 0; i < list.length; i++) {
    mapl.push(list[i] * 2);
}

声明式:

let list = [1, 2, 3, 4];
const double = x => x * 2;
list.map(double);

写代码最应该关注什么?

风格、效率、约定、使用场景、设计

Leftpad事件

事件本身的槽点:1、NPM模块粒度;2、代码风格;3、代码质量/效率

原代码:

function leftpad(str, len, ch) {
      str = String(str);
      var i = -1;
      if (!ch && ch !== 0) ch = ' ';
      len = len - str.length;
      while (++i < len) {
          str = ch + str;
      }
      return str;
  } 
  • 代码更简洁、效率提升
  function leftpad(str, len, ch) {
      str = "" + str;
      const padLen = len - str.length;
      if(padLen <= 0) {
        return str;
      }
      return (""+ch).repeat(padLen)+str;
  } 
  • 性能更好
  /*! https://mths.be/repeat v1.0.0 by @mathias */
​
  'use strict';
​
  var RequireObjectCoercible = require('es-abstract/2019/RequireObjectCoercible');
  var ToString = require('es-abstract/2019/ToString');
  var ToInteger = require('es-abstract/2019/ToInteger');
​
  module.exports = function repeat(count) {
    var O = RequireObjectCoercible(this);
    var string = ToString(O);
    var n = ToInteger(count);
    // Account for out-of-bounds indices
    if (n < 0 || n == Infinity) {
      throw RangeError('String.prototype.repeat argument must be greater than or equal to 0 and not be Infinity');
    }

    var result = '';
    while (n) {
      if (n % 2 == 1) {
        result += string;
      }
      if (n > 1) {
        string += string;
      }
      n >>= 1;
    }
    return result;
  };

交通灯切换

交通灯的切换可以有多种实现方法。以下是我不太熟悉的一种
异步 + 函数式实现 code.juejin.cn/pen/7108196…

总结

通过这次的学习,收获了如何逐渐优化代码来实现相同的场景。了解到了算法对于JavaScript很重要,今后会在算法上多下功夫。