如何写好JavaScript|青训营

95 阅读5分钟

写好JS的原则

  1. 各司其责-分离HTML/CSS/JS的职能
  2. 组件封装-好的UI组件具备正确性、拓展性、复用性。
  3. 过程抽象-应用函数式编程思想

一、各司其责

写一段JS控制一个网页,支持夜间模式切换

js1.png

  • 版本一:
          const btn = document.getElementById('modeBtn');
          btn.addEventListener('click', (e) => {
            const body = document.body;
            if(e.target.innerHTML === '🌞') {
              body.style.backgroundColor = 'black';
              body.style.color = 'white';
              e.target.innerHTML = '🌜';
            } else {
              body.style.backgroundColor = 'white';
              body.style.color = 'black';
              e.target.innerHTML = '🌞';
            }
          });

用JS做了CSS应该做的事情,混在一起写不方便后续的修改

  • 版本二:
          const btn = document.getElementById('modeBtn');
          btn.addEventListener('click', (e) => {
            const body = document.body;
            if(body.className !== 'night') {
              body.className = 'night';
            } else {
              body.className = '';
            }
          });

符合各司其责的原则

  • 版本三:
          <input id="modeCheckBox" type="checkbox">
          <div class="content">
            <header>
              <label id="modeBtn" for="modeCheckBox"></label>
              <h1>深夜食堂</h1>
            </header>
            <main>
              ......
            </main>
          </div>

          #modeCheckBox {
            display: none;
          }

          #modeCheckBox:checked + .content {
            background-color: black;
            color: white;
            transition: all 1s;
          }

仅用CSS控制样式,不使用JS

小结

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

二、组件封装

用原生JS写一个电商网站的轮播图

1、结构HTML

HTML,轮播图是一个典型的列表结构,可以用无序列表ul>li*4来实现。

            <div id="my-slider" class="slider-list">
              <ul>
                <li class="slider-list__item--selected">
                  <img src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/490a9fda06c042b2b00d4505b324b996~tplv-k3u1fbpfcp-zoom-1.image"/>
                </li>
                <li class="slider-list__item">
                  <img src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/b30362f1e345488a82fb4562b85aca89~tplv-k3u1fbpfcp-zoom-1.image"/>
                </li>
                <li class="slider-list__item">
                  <img src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/17a15493fd0f46f48fe51244098a8b8b~tplv-k3u1fbpfcp-zoom-1.image"/>
                </li>
                <li class="slider-list__item">
                  <img src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/3d4b18ffcb5447d192f264548ee0ff50~tplv-k3u1fbpfcp-zoom-1.image"/>
                </li>
              </ul>
            </div>

2、表现CSS

  • 使用CSS结对定位将图片重叠在同一位置
  • 轮播图切换的状态使用修饰符(modifier)
  • 轮播图的切换动画使用CSS transition
          #my-slider{
            position: relative;
            width: 790px;
          }

          .slider-list ul{
            list-style-type:none;
            position: relative;
            padding: 0;
            margin: 0;
          }

          .slider-list__item,
          .slider-list__item--selected{
            position: absolute;
            transition: opacity 1s;
            opacity: 0;
            text-align: center;
          }

          .slider-list__item--selected{
            transition: opacity 1s;
            opacity: 1;
          }

3、行为JS

API的设计应保证原子操作,职能单一,满足灵活性

js2.png


class Slider{
 constructor(id){
   this.container = document.getElementById(id);
   this.items = this.container
   .querySelectorAll('.slider-list__item, .slider-list__item--selected');
 }
 getSelectedItem(){
   const selected = this.container
     .querySelector('.slider-list__item--selected');
   return selected
 }
 getSelectedItemIndex(){
   return Array.from(this.items).indexOf(this.getSelectedItem());
 }
 slideTo(idx){
   const selected = this.getSelectedItem();
   if(selected){ 
     selected.className = 'slider-list__item';
   }
   const item = this.items[idx];
   if(item){
     item.className = 'slider-list__item--selected';
   }
 }
 slideNext(){
   const currentIdx = this.getSelectedItemIndex();
   const nextIdx = (currentIdx + 1) % this.items.length;
   this.slideTo(nextIdx);
 }
 slidePrevious(){
   const currentIdx = this.getSelectedItemIndex();
   const previousIdx = (this.items.length + currentIdx - 1)
     % this.items.length;
   this.slideTo(previousIdx);  
 }
}
const slider = new Slider('my-slider');
slider.slideTo(3);
//setInterval(()=>{
//    slider.slideNext();
//  },2000);
行为:控制流
  • 使用自定义事件来解耦

4、小结:基本方法

  • 结构设计
  • 展现效果
  • 行为设计
  1. API (功能)
  2. Event (控制流)

5、重构:插件化

在JS代码中,一个方法一般来说最多只能有15行代码,超过了就需要重构。为了便于代码的修改和优化,引入了插件化。

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

6、重构:模板化

将HTML模板化,更易于拓展

7、组件框架

抽象:
将组件通用模型抽象出来。

8、总结

  • 组件设计的原则:封装性、正确性、扩展性、复用性

  • 实现组件的步骤:结构设计、展现效果、行为设计

  • 三次重构

    1. 插件化
    2. 模板化
    3. 抽象化(组件框架)

三、过程抽象

  • 用来处理局部细节控制的一些方法
  • 函数式编程思想的基础应用

js3.png

1、高阶函数

HOF

  • 以函数作为参数
  • 以函数作为返回值
  • 常用于作为函数装饰器
         function HOF0(fn) {
           return function(...args) {
             return fn.apply(this, args);
           }
         }

常用高阶函数

Once
         function once(fn) {
           return function(...args) {
             if(fn) {
               const ret = fn.apply(this, args);
               fn = null;
               return ret;
             }
           }
         }
Throttle 节流函数

限制频率

function throttle(fn, time = 500){
  let timer;
  return function(...args){
    if(timer == null){
      fn.apply(this,  args);
      timer = setTimeout(() => {
        timer = null;
      }, time)
    }
  }
}
debounce 防抖

抖动时不保存信息,静止1s后保存信息

function debounce(fn, dur){
  dur = dur || 100;
  var timer;
  return function(){
    clearTimeout(timer);
    timer = setTimeout(() => {
      fn.apply(this, arguments);
    }, dur);
  }
}
Consumer
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 可迭代方法
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]);
  }
}

2、编程范式

  • 命令式与声明式

js4.png

一些提高JavaScript编写质量的建议:

  1. 理解基本语法和核心概念:熟悉JavaScript的基本语法、数据类型、运算符、条件语句、循环和函数等核心概念。了解JavaScript的面向对象编程(OOP)特性,如原型继承和构造函数。
  2. 使用一致的命名和代码风格:采用一致的命名约定和代码风格,使代码易于阅读和维护。使用有意义的变量和函数名,遵循驼峰命名法或下划线命名法。使用缩进、空格和注释来提高代码的可读性。
  3. 避免全局变量污染:尽量避免使用全局变量,因为全局变量容易导致命名冲突和代码耦合。使用模块化的方式组织代码,将功能封装在模块或命名空间中,以减少全局变量的使用。
  4. 异常处理:合理处理异常和错误情况,使用try-catch语句捕获和处理异常。避免在代码中使用过多的try-catch块,而是将异常处理放在合适的位置,以提高代码的可读性和性能。
  5. 避免重复代码:尽量避免重复的代码块,使用函数和循环来实现代码的重用。将常用的功能封装成函数或类,以便在需要时进行调用。
  6. 使用合适的数据结构和算法:根据问题的需求选择合适的数据结构和算法。了解数组、对象、集合、映射等数据结构的特性和适用场景。学习和应用常见的算法,如排序、搜索和遍历算法,以提高代码的效率和性能。
  7. 优化性能:注意代码的性能问题,避免不必要的循环和递归,减少DOM操作的次数,合并和压缩JavaScript文件,使用异步编程和延迟加载等技术来提高页面加载和执行速度。
  8. 测试和调试:编写测试用例来验证代码的正确性和稳定性。使用浏览器的开发者工具进行调试,利用断点、日志和性能分析工具来定位和解决问题。
  9. 持续学习和更新:JavaScript是一门不断发展和演进的语言,保持学习的态度,关注最新的语言特性和最佳实践。参与开源项目、阅读优秀的代码和技术文章,与其他开发者交流和分享经验。