This is the first day of my participation in the August More Text Challenge

Write good JS principles

  1. Do their job
  2. Component packaging
  3. Procedural abstraction

Do their job

There are three magic weapons: HTML, CSS, JS. We want to let it play its role, HTML is responsible for defining the structure of the web page, CSS is responsible for displaying the style of each element, JS is responsible for the behavior of the web page interaction. In line with the principle of each doing his or her own job, we will realize the function of the rotation chart:

version1

  1. Define the HTML structure first:
<div id="mySlider" class="slider-list"> <ul> <li class="slider-list__item--selected"> <img src="https://gimg2.baidu.com/image_search/src=http%3A%2F%2Fbpic.588ku.com%2Fback_pic%2F03%2F89%2F41%2F2857d8f9b35472a.jp G&refer=http%3A%2F%2Fbpic.588ku.com & app = 2002 & size = f9999, 10000 & q = a80 & n = 0 & g = 0 n & FMT = jpeg? The SEC = 1632303465 & t = 17 efe3a5d18cb60df 2a0dbf2e3008ce5" alt=""> </li> <li class="slider-list__item"> <img src="https://gimg2.baidu.com/image_search/src=http%3A%2F%2Fku.90sjimg.com%2Fback_pic%2F03%2F72%2F40%2F0257b93bd1c142f.jp G&refer=http%3A%2F%2Fku.90sjimg.com & app = 2002 & size = f9999, 10000 & q = a80 & n = 0 & g = 0 n & FMT = jpeg? = 1632303465 & t = bb0a16e7b527c1bf7 the SEC bad8d9b0e9e81b2" alt=""> </li> <li class="slider-list__item"> <img src="https://gimg2.baidu.com/image_search/src=http%3A%2F%2Fbpic.588ku.com%2Fback_pic%2F03%2F89%2F41%2F2857d8f9b35472a.jp G&refer=http%3A%2F%2Fbpic.588ku.com & app = 2002 & size = f9999, 10000 & q = a80 & n = 0 & g = 0 n & FMT = jpeg? The SEC = 1632303465 & t = 17 efe3a5d18cb60df 2a0dbf2e3008ce5" alt=""> </li> </ul> <a class="slide-list__next"></a> <a class="slide-list__previous"></a> <div class="slide-list__control"> <span class="slide-list__control-buttons slide-list__control-buttons--selected"></span> <span class="slide-list__control-buttons"></span> <span class="slide-list__control-buttons"></span> </div> </div>Copy the code
  1. Set the style
*,html { margin: 0; padding: 0; } ul li { list-style: none; } .slider-list { position: relative; width: 1200px; height: 566px; } .slider-list ul{ position: relative; width: 100%; height: 100%; 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; } .slide-list__previous, .slide-list__next { display: block; position: absolute; top: 50%; margin-top: -25px; width: 30px; height: 50px; text-align: center; font-size: 24px; line-height: 50px; Background: Rgba (255, 255, 255, 0.5); cursor: pointer; opacity: 0; transition: opacity .5s; } .slider-list:hover .slide-list__previous, .slider-list:hover .slide-list__next { opacity: 1; } .slide-list__previous { left: 0; } .slide-list__next { right: 0; } .slide-list__previous:after { content: '<'; } .slide-list__next:after { content: '>'; } .slide-list__control{ position: relative; display: table; Background: rgba(255, 255, 255, 0.5); padding: 5px; border-radius: 12px; bottom: 30px; margin: auto; } .slide-list__control-buttons, .slide-list__control-buttons--selected{ display: inline-block; width: 15px; height: 15px; border-radius: 50%; margin: 0 5px; background-color: white; cursor: pointer; } .slide-list__control-buttons--selected { background-color: red; }Copy the code
  1. Js to achieve the rotation of interaction
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(); }); }} / / for the currently selected image getSelectedItem () {const selected = this. The container. The querySelector (' the 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('mySlider'); slider.start();Copy the code

Component packaging

What is a component? Components are extracted units on a page that contain templates (HTML), functions (JS), and styles (CSS).

In line with the principle of component encapsulation to look at the code, although the above implementation of the rotation diagram code has realized the function, with correctness, but there are big problems, there are a lot of interactive logic code in the constructor constructor, readability and scalability is low, so we can further optimize the JS code, Extract the control elements of the toggle diagram and abstract them into plug-ins, which are as follows:

version2

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; } // Register plugin registerPlugins(... plugins){ plugins.forEach(plugin => plugin(this)); }... } // Button 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(); }); }} / / the next switch plugin function pluginNext (the slider) {const next. = the slider container. QuerySelector (' slide - list__next '); if(next){ next.addEventListener('click', evt => { slider.stop(); slider.slideNext(); slider.start(); evt.preventDefault(); }); } } const slider = new Slider('mySlider'); slider.registerPlugins(pluginController, pluginPrevious, pluginNext); slider.start();Copy the code

What is a good component? Good components have encapsulation, correctness, extensibility, and reuse.

The version of the above version of the wheel cast diagram has achieved correctness and expansibility. The previous/next/small dot control elements are abstracted into plug-in functions, which can be injected as needed, but the reusability is not high. The unused plug-in HTML elements are still displayed on the page. New render function to dynamically generate HTML structure, the specific implementation code is as follows:

version3

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; } // The template generates HTML render() {const images = this.options.images; const content = images.map(image => ` <li class="slider-list__item"> <img src="${image}"/> </li> `.trim()); Return ` < ul > ${content. the join (' ')} < / ul > `} / / registered plug-in 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); }); }... }Copy the code

Version3 code has been improved a lot, after the HTML templates, had the correctness, extensibility and reusability, the function of the control elements are real decoupling, in a project is a good way to use, but if consider different projects in different business scenarios using, components encapsulation, extensibility and reusability is not strong enough, we still have room for improvement. The general component framework can be abstracted out as a parent class, and the function of rotation as a subclass. The specific code is as follows:

version4

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 => `
      <li class="slider-list__item">
        <img src="${image}"/>
      </li>    
    `.trim());
    
    return `<ul>${content.join('')}</ul>`;
  }
  ...
}
Copy the code

Version1 -> version2 -> version3 -> version4 code, from the function realization, step by step optimization and improvement, to achieve the correctness, scalability, reuse, encapsulation. The final version4 code is based on the good component principle, called good code.

Procedural abstraction

What is process abstraction? Process abstraction is the principle of coding and the basic application of functional programming ideas, which are used to deal with local detail control methods. Higher-order functions are concrete applications of process abstraction.

Let’s take a look at a concrete example. We have a checkbox list, and when we click on it, it will disappear, with an animation effect.

const list = document.querySelector('ul');
const buttons = list.querySelectorAll('button');
buttons.forEach((button)=> {
 button.addEventLisner('click', evt => {
    const target = evt.target;
    target.parentNode.className = 'completed';
    setTimeout(()=> {
       list.removeChild(target.parentNode)
    }, 2000)
 })
})
Copy the code

In the above scenario, frequent clicking will result in an error that dom does not exist, so it is necessary to limit the number of click operations and execute the event logic of click cancellation only once.

In order to make the “only Once” requirement cover different event handling, we can separate this requirement and implement a Once function as follows:

function once(fn){ return function(... args){ if(fn){ const ret = fn.apply(this, args); fn = null; return ret; } } } buttons.forEach((button)=> { button.addEventLisner('click', once(evt => { const target = evt.target; target.parentNode.className = 'completed'; setTimeout(()=> { list.removeChild(target.parentNode) }, 2000) }) }));Copy the code

Other higher-order functions, such as Once, Throttle throttling, Debounce anti-vibration, Consumer asynchronous consumption and Iterative iteration, are all programmed using process abstraction.

conclusion

If we want to write good JS code, we need to keep in mind the three principles of each role, component encapsulation, process abstraction. What does it really do if we improve the code step by step? Some methods, such as the need to modify the library file can use higher-order functions the idea of programming, the participation is the function, by rewriting the function to extend the corresponding function, and then return to the function, such as writing is a non-invasive, not going to move to the original code library file, accordingly achieve the goal of modified method library file.