第六届360前端星计划_JavaScript 从入门到放弃

主讲人

  • 月影
  • 360前端技术专家
  • 奇舞团
  1. 各司其职

关灯吃面例子:

版本一
light.onclick = function(evt) {
  if(light.style.backgroundColor !== 'green'){
    document.body.style.backgroundColor = '#000';
    document.body.style.color = '#fff';
    light.style.backgroundColor = 'green';
  }else{
    document.body.style.backgroundColor = '';
    document.body.style.color = '';
    light.style.backgroundColor = '';    
  }
}
//js直接操作css,违反各司其职原则
lightButton.onclick = function(evt) {
  if(main.className === 'light-on'){
    main.className = 'light-off';
  }else{
    main.className = 'light-on';
  }
}
//通过修改类名,没有直接操作但是js还是有可能有bug
html,body {
  margin: 0;
  padding: 0;
  width: 100%;
  height: 100%;
}

#light {
  display: none;
}

#main {
  position: relative;
  padding: 20px;
  width: 100%;
  height: 100%;
  background-color: #fff;
  color: #000;
  transition: all .5s;
}

#light:checked + #main {
  background-color: #000;
  color: #fff;
}

.pic {
  float: left;
  margin-right: 20px;
}
//通过css实现,但是有兼容性问题

今天回到家,
煮了点面吃,
一边吃面一边哭,
泪水滴落在碗里,
没有开灯。
    
  1. 复杂 UI 组件的设计

以轮播图为例介绍UI设计

  1. 结构设计
//1. 图片结构是一个列表型结构,所以主体用
    //2. 使用 css 绝对定位将图片重叠在同一个位置 //3. 轮播图切换的状态使用修饰符(modifier) //4. 轮播图的切换动画使用 css transition
  1. API 设计
  • getSlectedItem()
  • getSlectedItemIndex()
  • slidTo()
  • 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()
}, 3000)
  1. 控制流设计

  
  
const detail = {index: idx}
const event = new CustomEvent('slide', {bubbles:true, detail})
this.container.dispatchEvent(event)
//用自定义事件进行触发

具体实现

class Slider{
  constructor(id, cycle = 3000){
    this.container = document.getElementById(id);
    this.items = this.container.querySelectorAll('.slider-list__item, .slider-list__item--selected');
    this.cycle = cycle;
const controller = this.container.querySelector('.slide-list__control');
if(controller){
  const buttons = controller.querySelectorAll('.slide-list__control-buttons, .slide-list__control-buttons--selected');
  controller.addEventListener('mouseover', evt=>{
    const idx = Array.from(buttons).indexOf(evt.target);
    if(idx >= 0){
      this.slideTo(idx);
      this.stop();
    }
  });
  
  controller.addEventListener('mouseout', evt=>{
    this.start();
  });
  
  this.container.addEventListener('slide', evt => {
    const idx = evt.detail.index
    const selected = controller.querySelector('.slide-list__control-buttons--selected');
    if(selected) selected.className = 'slide-list__control-buttons';
    buttons[idx].className = 'slide-list__control-buttons--selected';
  })
}

const previous = this.container.querySelector('.slide-list__previous');
if(previous){
  previous.addEventListener('click', evt => {
    this.stop();
    this.slidePrevious();
    this.start();
    evt.preventDefault();
  });
}

const next = this.container.querySelector('.slide-list__next');
if(next){
  next.addEventListener('click', evt => {
    this.stop();
    this.slideNext();
    this.start();
    evt.preventDefault();
  });
}
}
  getSelectedItem(){
    let selected = this.container.querySelector('.slider-list__item--selected');
    return selected
  }
  getSelectedItemIndex(){
    return Array.from(this.items).indexOf(this.getSelectedItem());
  }
  slideTo(idx){
    let selected = this.getSelectedItem();
    if(selected){ 
      selected.className = 'slider-list__item';
    }
    let item = this.items[idx];
    if(item){
      item.className = 'slider-list__item--selected';
    }
const detail = {index: idx}
const event = new CustomEvent('slide', {bubbles:true, detail})
this.container.dispatchEvent(event)
slideNext(){
    let currentIdx = this.getSelectedItemIndex();
    let nextIdx = (currentIdx + 1) % this.items.length;
    this.slideTo(nextIdx);
  }
  slidePrevious(){
    let currentIdx = this.getSelectedItemIndex();
    let previousIdx = (this.items.length + currentIdx - 1) % this.items.length;
    this.slideTo(previousIdx);  
  }
  start(){
    this.stop();
    this._timer = setInterval(()=>this.slideNext(), this.cycle);
  }
  stop(){
    clearInterval(this._timer);
  }
}

const slider = new Slider('my-slider');
slider.start();

优化1.:插件/依赖注入

  • 通过plugin注册函数
class Slider{
  constructor(id, cycle = 3000){
    this.container = document.getElementById(id);
    this.items = this.container.querySelectorAll('.slider-list__item, .slider-list__item--selected');
    this.cycle = cycle;
  }
  registerPlugins(...plugins){
    plugins.forEach(plugin => plugin(this));
  }
  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';
    }

    const detail = {index: idx}
    const event = new CustomEvent('slide', {bubbles:true, detail})
    this.container.dispatchEvent(event)
  }
  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);  
  }
  addEventListener(type, handler){
    this.container.addEventListener(type, handler)
  }
  start(){
    this.stop();
    this._timer = setInterval(()=>this.slideNext(), this.cycle);
  }
  stop(){
    clearInterval(this._timer);
  }
}

function pluginController(slider){
  const controller = slider.container.querySelector('.slide-list__control');
  if(controller){
    const buttons = controller.querySelectorAll('.slide-list__control-buttons, .slide-list__control-buttons--selected');
    controller.addEventListener('mouseover', evt=>{
      const idx = Array.from(buttons).indexOf(evt.target);
      if(idx >= 0){
        slider.slideTo(idx);
        slider.stop();
      }
    });

    controller.addEventListener('mouseout', evt=>{
      slider.start();
    });

    slider.addEventListener('slide', evt => {
      const idx = evt.detail.index
      const selected = controller.querySelector('.slide-list__control-buttons--selected');
      if(selected) selected.className = 'slide-list__control-buttons';
      buttons[idx].className = 'slide-list__control-buttons--selected';
    });
  }  
}

function pluginPrevious(slider){
  const previous = slider.container.querySelector('.slide-list__previous');
  if(previous){
    previous.addEventListener('click', evt => {
      slider.stop();
      slider.slidePrevious();
      slider.start();
      evt.preventDefault();
    });
  }  
}

function pluginNext(slider){
  const next = slider.container.querySelector('.slide-list__next');
  if(next){
    next.addEventListener('click', evt => {
      slider.stop();
      slider.slideNext();
      slider.start();
      evt.preventDefault();
    });
  }  
}


const slider = new Slider('my-slider');
slider.registerPlugins(pluginController, pluginPrevious, pluginNext);
slider.start();

优化2:改进插件
[图片上传失败...(image-37aaae-1586444695444)]

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 => `
      
  • `.trim()); return `
      ${content.join('')}
    `; } registerPlugins(...plugins){ plugins.forEach(plugin => { const pluginContainer = document.createElement('div'); pluginContainer.className = '.slider-list__plugin'; pluginContainer.innerHTML = plugin.render(this.options.images); this.container.appendChild(pluginContainer); plugin.action(this); }); } 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'; } let item = this.items[idx]; if(item){ item.className = 'slider-list__item--selected'; } const detail = {index: idx} const event = new CustomEvent('slide', {bubbles:true, detail}) this.container.dispatchEvent(event) } 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); } addEventListener(type, handler){ this.container.addEventListener(type, handler); } start(){ this.stop(); this._timer = setInterval(()=>this.slideNext(), this.cycle); } stop(){ clearInterval(this._timer); } } const pluginController = { render(images){ return `
    ${images.map((image, i) => ` `).join('')}
    `.trim(); }, action(slider){ const controller = slider.container.querySelector('.slide-list__control'); if(controller){ const buttons = controller.querySelectorAll('.slide-list__control-buttons, .slide-list__control-buttons--selected'); controller.addEventListener('mouseover', evt => { const idx = Array.from(buttons).indexOf(evt.target); if(idx >= 0){ slider.slideTo(idx); slider.stop(); } }); controller.addEventListener('mouseout', evt => { slider.start(); }); slider.addEventListener('slide', evt => { const idx = evt.detail.index const selected = controller.querySelector('.slide-list__control-buttons--selected'); if(selected) selected.className = 'slide-list__control-buttons'; buttons[idx].className = 'slide-list__control-buttons--selected'; }); } } }; const pluginPrevious = { render(){ return ``; }, action(slider){ const previous = slider.container.querySelector('.slide-list__previous'); if(previous){ previous.addEventListener('click', evt => { slider.stop(); slider.slidePrevious(); slider.start(); evt.preventDefault(); }); } } }; const pluginNext = { render(){ return ``; }, action(slider){ const previous = slider.container.querySelector('.slide-list__next'); if(previous){ previous.addEventListener('click', evt => { slider.stop(); slider.slideNext(); slider.start(); evt.preventDefault(); }); } } }; const slider = new Slider('my-slider', {images: ['https://p5.ssl.qhimg.com/t0119c74624763dd070.png', 'https://p4.ssl.qhimg.com/t01adbe3351db853eb3.jpg', 'https://p2.ssl.qhimg.com/t01645cd5ba0c3b60cb.jpg', 'https://p4.ssl.qhimg.com/t01331ac159b58f5478.jpg'], cycle:3000}); slider.registerPlugins(pluginController, pluginPrevious, pluginNext); slider.start();

    优化3:组件模型抽象
    [图片上传失败...(image-b21c0a-1586444695444)]

    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 ''
      }
    }
    
    class Slider extends Component{
      constructor(id, opts = {name: 'slider-list', data:[], cycle: 3000}){
        super(id, opts);
        this.items = this.container.querySelectorAll('.slider-list__item, .slider-list__item--selected');
        this.cycle = opts.cycle || 3000;
        this.slideTo(0);
      }
      render(data){
        const content = data.map(image => `
          
  • `.trim()); return `
      ${content.join('')}
    `; } 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'; } const detail = {index: idx} const event = new CustomEvent('slide', {bubbles:true, detail}) this.container.dispatchEvent(event) } 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); } addEventListener(type, handler){ this.container.addEventListener(type, handler); } start(){ this.stop(); this._timer = setInterval(()=>this.slideNext(), this.cycle); } stop(){ clearInterval(this._timer); } } const pluginController = { render(images){ return `
    ${images.map((image, i) => ` `).join('')}
    `.trim(); }, action(slider){ let controller = slider.container.querySelector('.slide-list__control'); if(controller){ let buttons = controller.querySelectorAll('.slide-list__control-buttons, .slide-list__control-buttons--selected'); controller.addEventListener('mouseover', evt=>{ var idx = Array.from(buttons).indexOf(evt.target); if(idx >= 0){ slider.slideTo(idx); slider.stop(); } }); controller.addEventListener('mouseout', evt=>{ slider.start(); }); slider.addEventListener('slide', evt => { const idx = evt.detail.index; let selected = controller.querySelector('.slide-list__control-buttons--selected'); if(selected) selected.className = 'slide-list__control-buttons'; buttons[idx].className = 'slide-list__control-buttons--selected'; }); } } }; const pluginPrevious = { render(){ return ``; }, action(slider){ let previous = slider.container.querySelector('.slide-list__previous'); if(previous){ previous.addEventListener('click', evt => { slider.stop(); slider.slidePrevious(); slider.start(); evt.preventDefault(); }); } } }; const pluginNext = { render(){ return ``; }, action(slider){ let previous = slider.container.querySelector('.slide-list__next'); if(previous){ previous.addEventListener('click', evt => { slider.stop(); slider.slideNext(); slider.start(); evt.preventDefault(); }); } } }; const slider = new Slider('my-slider', {name: 'slide-list', data: ['https://p5.ssl.qhimg.com/t0119c74624763dd070.png', 'https://p4.ssl.qhimg.com/t01adbe3351db853eb3.jpg', 'https://p2.ssl.qhimg.com/t01645cd5ba0c3b60cb.jpg', 'https://p4.ssl.qhimg.com/t01331ac159b58f5478.jpg'], cycle:3000}); slider.registerPlugins(pluginController, pluginPrevious, pluginNext); slider.start();
    1. 第三节:局部细节控制

    实例一

    block.onclick = function(evt){
      console.log('hide');
      evt.target.className = 'hide';
      setTimeout(function(){
        document.body.removeChild(block);
      }, 2000);
    };
    执行多次有问题
    
    //如何进行统一的抽象
    block.onclick = function(evt){
      block.onclick = null;
      console.log('hide');
      evt.target.className = 'hide';
      setTimeout(function(){
        document.body.removeChild(block);
      }, 2000);
    };
    
    //获取异步请求
    const api = 'https://test.h5jun.com/index/gaobai?text=';
    submitBtn.onclick = async function(evt){
      evt.preventDefault();
     
      let {data} = await axios.get(api + t.value);
      gaobai.src = 'data:image/jpeg;base64,' + data.data;
      console.log('data:image/jpeg;base64,' + data.data)
    }
    
    function once(fn){
      return function(...args){
        if(fn){
          let ret = fn.apply(this, args);
          fn = null;
          return ret;
        }
      }
    }
    
    function foo(idx){
      console.log(`I'm called:${idx}`);
    }
    
    foo(0);
    foo(1);
    foo(2);
    
    foo = once(foo);
    
    foo(3);
    foo(4);
    foo(5);
    

    节流:

    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:

    function debounce(fn){
      let timer = null
      return function(...args){
        if(timer != null) {
          clearTimeout(timer)
        }
        timer = setTimeout(() => {
          fn.apply(this, args)
          timer = null
        }, 300)
      }
    }
    
    const api = 'https://test.h5jun.com/index/gaobai?text=';
    
    submitBtn.onclick = debounce(async function(evt){
      evt.preventDefault();
      
      let {data} = await axios.get(api + t.value);
      gaobai.src = 'data:image/jpeg;base64,' + data.data;
      console.log('data:image/jpeg;base64,' + data.data)
    })
    

    消费者模式(把数据压缩到一起,再异步执行)

    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(x, y){
      let sum = x + y;
      console.log(sum);
      return sum;
    }
    
    let consumerAdd = consumer(add, 1000);
    
    let sum = 0;
    for(let i = 0; i < 10; i++){
      consumerAdd(sum, i);
    }
    

    程序语言里面:

    • 指令式,面向过程,面向对象
    • 声明式,逻辑的函数式的

    js是指令式编程但有函数式特点
    reduce方法

    function add(x, y){
    
    

    addMany 通用度不高

    function add(x, y){
      return x + y;
    }
    
    function sub(x, y){
      return x - y;
    }
    
    function addMany(...args){
      return args.reduce(add);
    }
    
    function subMany(...args){
      return args.reduce(sub);
    }
    
    console.log(addMany(1,2,3,4));
    console.log(subMany(1,2,3,4));
    

    iterative方法高阶函数 封装其他操作

    function iterative(fn){
      return function(...args){
        return args.reduce(fn.bind(this));
      }
    }
    
    const add = iterative((x, y) => x + y);
    const sub = iterative((x, y) => x - y);
    
    console.log(add(1,2,3,4));
    console.log(sub(1,2,3,4));
    

    高阶函数:

    自身输入是个函数 或返回函数

    点击实例

    不通用

    例:

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

    1.提供一个toggl函数

    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'
    );
    

    方便扩展,直接增加状态就可以

    不需要修改逻辑

    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 = 'warn',
      evt => evt.target.className = 'off',
    evt => evt.target.className = 'on'
    );
    

    2.把loop过程抽象出来

    function * loop(list, max = Infinity){
      let i = 0;
      
      //noprotect
      while(i < max){
        yield list[i++ % list.length];
      }
    }
    
    
    function toggle(...actions){
      let action = loop(actions);
      return function(...args){
        return action.next().value.apply(this, args);
      }
    }
    
    switcher.onclick = toggle(
      evt => evt.target.className = 'warn',
      evt => evt.target.className = 'off',
      evt => evt.target.className = 'on'
    );
    

    总结 :

    1. 各司其职:JavaScript 尽量只做状态管理
    2. 结构、API、控制流分离设计 UI 组件
    3. 插件和模板化,并抽象出组件模型
    4. 运用过程抽象的技巧来抽象并优化局部 API

    你可能感兴趣的:(第六届360前端星计划_JavaScript 从入门到放弃)