[JavaScript | 青训营笔记]

75 阅读4分钟

[JavaScript | 青训营笔记]

这是我参与「第五届青训营」伴学笔记创作活动的第 2 天 🎉

📌 课程主要内容

📌 写好 JS 的三大原则

📃 各司其职

为了更清晰地解释这一原则,让我们开始于一个简单的案例

💼 案例

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

📋 方案 I

codepen.io/qzhihe-the-…

🧠 实现思路

核心 JS 代码如下:

 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 代码中直接对 body 的 CSS 进行了修改

📋 方案 II

codepen.io/qzhihe-the-…

🧠 实现思路

核心 JS 代码如下:

 const btn = document.getElementById('modeBtn');
 btn.addEventListener('click', (e) => {
   const body = document.body;
   if (body.className !== 'night') {
     body.className = 'night';
   } else {
     body.className = '';
   }
 });
🍰 优点
  • 没有对元素的 CSS 进行直接的操作,而是通过修改元素的类名间接实现了页面样式的变化
💊 存在问题
  • 对于仅页面样式的改变仍是由 JS 实现的,没有完全的做到各司其职

📋 方案 III

codepen.io/qzhihe-the-…

🧠 实现思路

在 HTML 中:

  • 添加一个 type="checkbox"input,并用一个 label 与之相关联

在 CSS 中:

  • 利用 label::after 选择器使其显示为 ☀️ 或 🌙
  • 利用 display: none;input 隐藏
  • 通过 input 标签的 :checked 选择器实现正文部分背景和文字颜色的变更,以及日/夜模式图标的变化

核心 CSS 代码如下:

 #modeCheckBox:checked + .content {
   background-color: black;
   color: white;
 }
🍰 优点
  • 页面整体样式变化的逻辑不再由 JS 实现,而是利用 CSS 的伪类选择器来实现,真正做到了各司其职

📦 总结

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

📃 组件封装

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

好的组件应具备:

  • 封装性
  • 正确性
  • 扩展性
  • 复用性

为了更直观体现出封装组件的重要性,让我们仍用一个案例作为开始

💼 案例

用原生 JS 写一个电商网站的轮播图,应该如何实现

📋 方案 I

codepen.io/qzhihe-the-…

🧠 实现思路

在 HTML 中:

  • 使用 ul 无序列表存放图片
  • 利用其它标签实现轮播图的翻页控件和控制条
  • 为当前图片与非当前图片添加不同的类名
  • 为控制条中的已选中按钮和未选中按钮添加不同的类名

样式:

  • ul 设置 list-style-type: none; 取消掉原有列表样式
  • 利用绝对定位,令所有图片重叠在同一位置
  • 利用 transition 为轮播图的切换添加动画
  • 利用绝对定位,调整翻页控件和控制条的位置
  • 利用 :hover 状态选择器,实现鼠标移入和移出时翻页控件的显示与隐藏
  • 微调细节

行为:

  • 编写一个 Slider 类,其内包含以下方法:

    • constructor (id, cycle = 3000) {...},构造方法,初始化必要数据
    • getSelectedItem () {...},返回当前轮播图片的 DOM 节点
    • getSelectedItemIndex () {...},返回当前轮播图片的索引
    • slideTo (idx) {...},切换到指定索引的图片
    • slideNext () {...},切换至下一张图片
    • slidePrevious () {...},切换至上一张图片
    • start () {...},开始轮播
    • stop () {...},停止轮播

📃 过程抽象

过程抽象,是用于处理局部细节控制的方法,是函数式编程思想的基础应用

为了进一步理解何为过程抽象,我们仍然以一个案例开始

💼 案例 I

📋 方案

🧠 实现思路

核心 JS 代码如下:

 function once () {
   return function (...args) {
     if (fn) {
       const ret = fn.apply(this, args);
       fn = null;
       return ret;
     }
   }
 }
🍰 优点
  • 将"只执行某种行为一次"抽象为了一个独立的函数,这样不仅降低了耦合,还实现了封装和复用

📈 高阶函数

在上述案例中,once 函数是采用了一种名叫高阶函数(HOF) 的方法实现的,它具备以下特点:

  • 以函数作为参数
  • 以函数作为返回值
  • 常用于作为函数装饰器
常用高阶函数
once
 function once () {
   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) {
     fn.apply(this, arge);
     timer = setTimeout(() => {
       timer = null;
     }, timer);
   }
 }
debounce
 function debounce (fn, dur) {
   dur = dur || 100;
   var timer;
   return function () {
     clearTimeout(timer);
     timer = setTimeout(() => {
       fn.apply(this, arguments);
     }, dur);
   }
 }
consumer / 2
 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]);
   }
 }

💼 案例 II