[ JS | 青训营笔记]

92 阅读3分钟

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

一、主要要点

  • 写好JS的原则
  • 组件封装---轮播图
  • 过程抽象
  • 高阶函数
  • 编程范式

二、详细介绍

2.1 写好JS原则

  • 各司其责

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

  • 过程抽象

下面就来说一下组件封装以及过程抽象.

2.2 组件封装--以轮播图为例

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

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

注意:将html、css、js代码copy即可运行,js文件引用时,要放在html结构的下面。

结构设计:HTML

轮播图是一个典型的列表结构,我们可以使用无序列表ul元素来实现.

<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/85f068e8d7824908a68b03af694f4195~tplv-k3u1fbpfcp-zoom-1.image" />
      </li>
      <li class="slider-list__item">
        <img src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/967634ea049343dea6cd530cfe0edad5~tplv-k3u1fbpfcp-zoom-1.image" />
      </li>
      <li class="slider-list__item">
        <img src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/45ad0d7b1d0a484a9076aafdb8ebb732~tplv-k3u1fbpfcp-zoom-1.image" />
      </li>
      <li class="slider-list__item">
        <img src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/c6cbab4d58a4407494a1aa01f79c6796~tplv-k3u1fbpfcp-zoom-1.image" />
      </li>
    </ul>
  </div>

展现效果:CSS

  • 使用CSS绝对定位将图片重叠在同一个位置
  • 轮播图切换的状态使用修饰符(modofier)
  • 轮播图的切换动画使用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;
}

行为设计(JS)

API

  • API设计应保证原子操作,职责单一,满足灵活性
  • Slider
    • getSelectedItem() ---------------------得到当前选中的图片元素
    • getSelectedItemIndex() ----------------得到当前选中图片元素在列表中的下标
    • slide To()-----------------------------到某个特定index的元素上
    • slideNext()----------------------------向后轮播下一张图片
    • slidePrevious()------------------------向前轮播上一张图片
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')
setInterval(() => {
  slider.slideNext()
}, 2000)

以上html+css+js实现的效果缺少左右按钮和底部圆点

控制流

添加左右按钮和底部圆点

  • 使用自定义事件来解耦

完整代码:

重构:插件化

解耦

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

重构:模板化

解耦

  • 将HTML模板化,封装在自定义函数render()内,更易于扩展

重构:组件框架

抽象

  • 将通用的组件模型抽象出来 完整代码:

2.3 过程抽象

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

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

    image.png

举例:操作次数限制

  • 一些异步交互

  • 一次性的HTTP请求

    image.png

2.4 高阶函数

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

常用高阶函数

image.png

image.png

once

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

image.png

Throttle

地址:code.h5jun.com/gale/1/edit…

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

btn.onclick = throttle(function(e){
 circle.innerHTML = parseInt(circle.innerHTML) + 1;
 circle.className = 'fade';
 setTimeout(() => circle.className = '', 250);
});

Debounce

地址:code.h5jun.com/wik/edit?js…

var i = 0;
setInterval(function(){
  bird.className = "sprite " + 'bird' + ((i++) % 3);
}, 1000/10);

function debounce(fn, dur){
  dur = dur || 100;
  var timer;
  return function(){
    clearTimeout(timer);
    timer = setTimeout(() => {
      fn.apply(this, arguments);
    }, dur);
  }
}

document.addEventListener('mousemove', debounce(function(evt){
  var x = evt.clientX,
      y = evt.clientY,
      x0 = bird.offsetLeft,
      y0 = bird.offsetTop;
  
  console.log(x, y);
  
  var a1 = new Animator(1000, function(ep){
    bird.style.top = y0 + ep * (y - y0) + 'px';
    bird.style.left = x0 + ep * (x - x0) + 'px';
  }, p => p * p);
  
  a1.animate();
}, 100));

Consumer/2

地址:code.h5jun.com/roka/7/edit…

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)
    }
  }
}

function add(ref, x){
  const v = ref.value + x;
  console.log(`${ref.value} + ${x} = ${v}`);
  ref.value = v;
  return ref;
}

let consumerAdd = consumer(add, 1000);

const ref = {value: 0};
for(let i = 0; i < 10; i++){
  consumerAdd(ref, i);
}

Iterative

地址:code.h5jun.com/kapef/edit?…

const isIterable = obj => obj != null 
  && typeof obj[Symbol.iterator] === 'function';

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]);
  }
}

const setColor = iterative((el, color) => {
  el.style.color = color;
});

const els = document.querySelectorAll('li:nth-child(2n+1)');
setColor(els, 'red');

2.5 编程范式

image.png

  • 命令式

image.png

  • 声明式

image.png

例子:Toggle

1.命令式

  if(evt.target.className === 'on'){
    evt.target.className = 'off';
  }else{
    evt.target.className = 'on';
  }
}

2.声明式

function toggle(...actions){
  return function(...args){
    let action = actions.shift();
    actions.push(action);
    return action.apply(this, args);
  }
}

switcher.onclick = toggle(
  evt => evt.target.className = 'off',
  evt => evt.target.className = 'on'
);

三、 总结

再次体验到了组件封装的重要性,在写JS的同时,代码优化也极为重要;这节课中,也再次重温理解了高阶函数,对防抖和节流又有了新的认识。