前端基础:JavaScript(一)| 青训营笔记

80 阅读3分钟

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

1 写好JS的原则

  • 各司其职
    • 让HTML、CSS和JavaScript职能分离
  • 组件封装
    • 好的UI组件具备正确性、扩展性和复用性
  • 过程抽象
    • 应用函数式编程思想

1.1 各司其责

写一段代码,控制网页进行深浅色模式转换

  • 版本一
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代码,因为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 = '';
    }
  });

思考:这一版比上一班好在哪里?还有没有其他方案?
这版将CSS样式与JS分离,若后期需改动,直接更改CSS样式中的代码即可

  • 版本三
<input id="modeCheckBox" type="checkbox">
  <div class="content">
    <header>
      <label id="modeBtn" for="modeCheckBox"></label>
      <h1>深夜食堂</h1>
    </header>
    <main>
      <div class="pic">
        <img src="https://p2.ssl.qhimg.com/t0120cc20854dc91c1e.jpg">
      </div>
      <div class="description">
        <p>
            这是一间营业时间从午夜十二点到早上七点的特殊食堂。这里的老板,不太爱说话,却总叫人吃得热泪盈
            眶。在这里,自卑的舞蹈演员偶遇隐退多年舞界前辈,前辈不惜讲述自己不堪回首的经历不断鼓舞年轻人,最终令其重拾自信;轻言绝交的闺蜜因为吃到共同喜爱的美食,回忆起从前的友谊,重归于好;乐观的绝症患者遇到同命相连的女孩,两人相爱并相互给予力量,陪伴彼此完美地走过了最后一程;一味追求事业成功的白领,在这里结交了真正暖心的朋友,发现真情比成功更有意义。食物、故事、真情,汇聚了整部剧的主题,教会人们坦然面对得失,对生活充满期许和热情。每一个故事背后都饱含深情,情节跌宕起伏,令人流连忘返 [6]  。
        </p>
      </div>
    </main>
  </div>
  #modeCheckBox {
    display: none;
  }

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

直接使用CSS代码来控制CSS样式

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

1.2 组件封装

 组件是指web页面上抽出来一个个包含模板(HTML)、功能(JS)和样式(CSS)的单元。好的组件具备封装性、正确性、扩展性、复用性<br>

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

问题:不够灵活,当改动某项功能时,需改动的地方较多 解决方法:重构

  • 重构:插件化
    • 解耦
      • 将控制元素抽取成插件
      • 插件与组件之间通过依赖注入方式建立联系
    • 问题:当修改或添加某个插件时,还需要更改HTML中的结构
  • 重构:模板化
    • 解耦
      • 将HTML模板化,更易于扩展
      image.png
class Slider{
   constructor(id, opts = {images:[], cycle: 3000}){
     this.container = document.getElementById(id);
     this.options = opts;
     this.container.innerHTML = this.render();
     this.items = this.container.querySelectorAll('.slider-list__item, .slider-list__item--selected');
     this.cycle = opts.cycle || 3000;
     this.slideTo(0);
   }
   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>`;
   }
   ...
 }
  • 抽象化
    • 将组件通用模型抽象出来

      image.png

class Component{
   constructor(id, opts = {name, data:[]}){
     this.container = document.getElementById(id);
     this.options = opts;
     this.container.innerHTML = this.render(opts.data);
   }
   registerPlugins(...plugins){
     plugins.forEach(plugin => {
       const pluginContainer = document.createElement('div');
       pluginContainer.className = `.${name}__plugin`;
       pluginContainer.innerHTML = plugin.render(this.options.data);
       this.container.appendChild(pluginContainer);
       
       plugin.action(this);
     });
   }
   render(data) {
     /* abstract */
     return ''
   }
 }
  • 总结
    • 组件设计的原则:封装性、正确性、扩展性、复用性
    • 实现组件的步骤:结构设计、展现效果、行为设计
    • 三次重构:
      • 插件化
      • 模块化
      • 抽象化(组件框架)

1.3 过程抽象

 为了能够让“只执行一次”的需求覆盖不同的事件处理,我们可以将这个需求剥离出来,这个过程我们称为过程抽象
  • 过程抽象
    • 用来处理局部细节控制的一些方法
    • 函数式编程思想的基础应用

image.png

  function once(fn) {
    return function(...args) {
      if(fn) {
        const ret = fn.apply(this, args);
        fn = null;
        return ret;
      }
    }
  }
  • 高阶函数(HOF)
    • 以函数作为参数
    • 以函数作为返回值
    • 常用于作为函数修饰器

image.png

 function HOF0(fn) {
    return function(...args) {
      return fn.apply(this, args);
    }
  }
  • 常用高阶函数
    • Once
    • Throttle(节流函数):常用于Mousemove事件……
    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(防抖函数):自动保存功能
    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
        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)
          }
        }
      }
      
      btn.onclick = consumer((evt)=>{
        let t = parseInt(count.innerHTML.slice(1)) + 1;
        count.innerHTML = `+${t}`;
        count.className = 'hit';
        let r = t * 7 % 256,
            g = t * 17 % 128,
            b = t * 31 % 128;
      
        count.style.color = `rgb(${r},${g},${b})`.trim();
        setTimeout(()=>{
          count.className = 'hide';
        }, 500);
      }, 800)
      
      
    • Iterative
      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');
      

1.5 编程范式

  • 命令式与声明式

    image.png

    //命令式
    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);
    
    • 声明式比命令式有更多的可扩展性