Preface 🌂

For the front-end, JS can be said to be one of the most common scripting languages. The most common code that front-end engineers write is probably JS code. But most of the time, we are just CV engineers, just write the code and run on the line, but it does not know that there is a problem is that we are a little negligent preparation, it is easy to cause the program unknowingly bug.

Therefore, to write good JS code, we must follow several principles: each role, component encapsulation and process abstraction.

Some time ago, I attended the byte youth training camp, and had the honor to hear the live broadcast of the moon shadow big guy 💁♂️. So next, we will follow the steps of moon shadow big guy, to understand how to write good JS ~

Let’s learn ~⛱️

☂️ What is good JS code?

Before we get into the three principles of writing good JS, let’s talk about what makes good JS code.

There are three principles that good JS code should follow. Respectively is:

  • Do their job
  • Component packaging
  • Procedural abstraction

Now, we will expand on these three principles.

Two, 🧵 write some principles of JS

1. Each plays its own part 👋

(1) Definition

Those of you who write the front end should know why the front end is divided into HTML, CSS and JS. So actually, in the front end, HTML is responsible for structure, CSS is responsible for presentation, and JS is responsible for behavior. As shown below:

(2) Illustration of examples

Let’s start with a problem. Let’s say we want to write a javascript and control a web page so that it can only be viewed in light and dark colors. As shown below:

What would you do if you were to do it?


Now, let’s look at the first version.

Let’s start with the HTML code:

<! DOCTYPEhtml>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="Width = device - width, initial - scale = 1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <title>Dining room in the middle of the night</title>
</head>
<body>
  <header>
    <button id="modeBtn">🌞</button>
    <h1>Dining room in the middle of the night</h1>
  </header>
  <main>
    <div class="pic">
      <img src="https://p2.ssl.qhimg.com/t0120cc20854dc91c1e.jpg">
    </div>
    <div class="description">
      <p>This is a special canteen that is open from midnight to 7 a.m. The boss here is not very talkative, but always makes people eat tears in their eyes. Here, the self-abasement dancer encounters a veteran dancer who has retired from the dance industry for many years. The veteran inspires young people by telling about his embarrassing experience, and finally makes them regain confidence. The bestie broke up because they ate their favorite food and recalled their former friendship. Optimistic terminally ill patient encounters the girl with the same destiny, two people love each other and give strength to each other, accompany each other to walk perfectly the last journey; Blind pursuit of career success of white-collar workers, here to make real warm heart friends, found that the truth is more meaningful than success. Food, stories and true feelings are the themes of the whole play, which teaches people to face gains and losses calmly and to be full of expectations and enthusiasm for life. Behind each story is full of deep feeling, plot ups and downs, unforgettable [6].</p>
    </div>
  </main>
</body>
</html>
Copy the code

Now look at the CSS code:

body.html {
  width: 100%;
  height: 100%;
  padding: 0;
  margin: 0;
  overflow: hidden;
}
body {
  padding: 10px;
  box-sizing: border-box;
}
div.pic img {
  width: 100%;
}
#modeBtn {
  font-size: 2rem;
  float: right;
  border: none;
  background: transparent;
}
Copy the code

Finally, let’s look at the JS code:

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 = '🌞'; }});Copy the code

Now, you can look at this version up here. You can think about it for a moment, what’s wrong with this version? If you were optimizing, what would you do?

There are several main problems with the above code:

  • There is no separation of style and behavior, i.e., no separation of responsibilities
  • Global pollution
  • Poor reusability
  • Poor encapsulation

In view of the above problems, we will optimize the second version. Details are as follows:

HTML code:

<! DOCTYPEhtml>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="Width = device - width, initial - scale = 1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <title>Dining room in the middle of the night</title>
</head>
<body>
  <header>
    <button id="modeBtn"></button>
    <h1>Dining room in the middle of the night</h1>
  </header>
  <main>
    <div class="pic">
      <img src="https://p2.ssl.qhimg.com/t0120cc20854dc91c1e.jpg">
    </div>
    <div class="description">
      <p>This is a special canteen that is open from midnight to 7 a.m. The boss here is not very talkative, but always makes people eat tears in their eyes. Here, the self-abasement dancer encounters a veteran dancer who has retired from the dance industry for many years. The veteran inspires young people by telling about his embarrassing experience, and finally makes them regain confidence. The bestie broke up because they ate their favorite food and recalled their former friendship. Optimistic terminally ill patient encounters the girl with the same destiny, two people love each other and give strength to each other, accompany each other to walk perfectly the last journey; Blind pursuit of career success of white-collar workers, here to make real warm heart friends, found that the truth is more meaningful than success. Food, stories and true feelings are the themes of the whole play, which teaches people to face gains and losses calmly and to be full of expectations and enthusiasm for life. Behind each story is full of deep feeling, plot ups and downs, unforgettable [6].</p>
    </div>
  </main>
</body>
</html>
Copy the code

The CSS code:

body.html {
  width: 100%;
  height: 100%;
  max-width: 600px;
  padding: 0;
  margin: 0;
  overflow: hidden;
}
body {
  padding: 10px;
  box-sizing: border-box;
  transition: all 1s;
}
div.pic img {
  width: 100%;
}
#modeBtn {
  font-size: 2rem;
  float: right;
  border: none;
  outline: none;
  cursor: pointer;
  background: inherit;
}

body.night {
  background-color: black;
  color: white;
  transition: all 1s;
}

#modeBtn::after {
  content: '🌞';
}
body.night #modeBtn::after {
  content: '🌜';
}
Copy the code

JS code:

const btn = document.getElementById('modeBtn');
btn.addEventListener('click'.(e) = > {
  const body = document.body;
  if(body.className ! = ='night') {
    body.className = 'night';
  } else {
    body.className = ' '; }});Copy the code

As you can see, we tried to pull styles out of CSS as much as possible, so that the responsibilities are more separate and the code looks better.

So if you look at this, you might think, is it possible to put everything in CSS instead of JS? The answer is yes. Now let’s look at the third version.


To further optimize, let’s look at the third version. The HTML code remains the same as above, with no JS code. The new CSS code is attached below:

body.html {
  width: 100%;
  height: 100%;
  max-width: 600px;
  padding: 0;
  margin: 0;
  overflow: hidden;
}

body {
  box-sizing: border-box;
}

.content {
  padding: 10px;
  transition: background-color 1s, color 1s;
}

div.pic img {
  width: 100%;
}

#modeCheckBox {
  display: none;
}

/* When selected, add a content class */
#modeCheckBox:checked + .content {
  background-color: black;
  color: white;
  transition: all 1s;
}

#modeBtn {
  font-size: 2rem;
  float: right;
}

#modeBtn::after {
  content: '🌞';
}

/* Modify the contents of the content class */
#modeCheckBox:checked + .content #modeBtn::after {
  content: '🌜';
}
Copy the code

You can see that when you get to the final version, you’re just going to have HTML and CSS, and you’re going to have a lot more simplicity.


Now, let’s summarize this example. The conclusion of the example of late night canteen is as follows 👇

  • HTML/CSSEach in his own place;
  • Unnecessary use should be avoidedJSTo manipulate styles directly;
  • You can useclassTo represent thestate;
  • Pure display class interaction seeks zeroJSSolutions.

By now, I believe you have a certain understanding of the above example.

At the same time, the above code has been posted on the online website code.h5jun, you can click the link below to obtain it according to your own needs

👉 Late night canteen: version one

👉 Late night canteen: version 2

👉 Late night canteen: version 3

2. Component encapsulation 🤏

(1) Definition

A component is a unit of a Web page that contains templates (HTML), functions (JS), and styles (CSS). Good components are encapsulation, correctness, extensibility, and reuse.

(2) Example description: multicast components

Using native JS to write an e-commerce site of the wheel broadcast map, how to achieve it?

Let’s take a look at what we want to achieve with the rotation component. The details are as follows:

So now, based on the above renderings, let’s do code design.


The first is structural design: HTML. The specific code is as follows:

<div id="my-slider" class="slider-list">
    <ul>
        <li class="slider-list__item--selected">
            <img src="https://p5.ssl.qhimg.com/t0119c74624763dd070.png" />
        </li>
        <li class="slider-list__item">
            <img src="https://p4.ssl.qhimg.com/t01abe3351db853eb3.jpg" />
        </li>
        <li class="slider-list__item">
            <img src="https://p2.ssl.qhimg.com/t01645cd5ba0c3b60cb.jpg" />
        </li>
        <li class="slider-list__item">
            <img src="https://p4.ssl.qhimg/t01331ac159b58f5478.jpg" />
        </li>
        <li class="https://slider-list__item">
            <img src="htttps://p4.ssl.qhimg.com/t01331ac159b58f5478.jpg" />
        </li>
    </ul>
</div>
Copy the code

As we can see from the above code, the wheel cast diagram is a typical list structure, which we can implement using unordered list

    elements.

Moving on, let’s design its presentation effect CSS. The specific code is as follows:

#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 {
    /* Use CSS absolute positioning to overlap images in the same position */
    position: absolute;
    transition: opacity 1s;
    opacity: 0;
    text-align: center;
}

/* The state of the carousel switch uses the modifier */
.slider-list__item--selected {
    /* Use CSS Transition */
    transition: opacity 1s;
    opacity: 1;
}
Copy the code

For the above designed CSS, there are mainly the following characteristics:

  • usecssAbsolute positioning overlaps images in the same position;
  • Use the modifier to switch the status of the rotation chart;
  • Use the toggle animation of the carousel graphCSS transition

Now let’s design its behavior, which is JS. The specific code is as follows:

class Slider {
    constructor(id) {
        this.container = document.getElementById(id);
        this.items = this.container
        .querySelectorAll('.slider-list__item, .slider-list__item--selected');
    }
    getSlectedItem() {
        const selected = this.container
        .querySelector('.slider-list__item--selected');
        return selected;
    }
    getSlectedItemIndex() {
        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.getSlectedItemIndex();
        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); }}Copy the code

JS is actually used to design some behavior API, that API design, it should try to ensure atomic operations, and responsibilities to a single, to meet the flexibility.

The API in the JS code above is shown below. The details are as follows:


In addition to the above, if you are careful, you should have noticed that there are a few dots in the switching section below, which we call the control flow. What about these control flows?

Take a look at the following code:

<a class="slide-list__next"></a>
<a class="slide-list__previous"></a>
<div class="slide-list__control">
    <span class="slide-list__control-buttons--selected"></span>
    <span class="slide-list__control-buttons"></span>
    <span class="slide-list__control-buttons"></span>
    <span class="slide-list__control-buttons"></span>
</div>
Copy the code

At the same time, we need to use custom events for decoupling. As follows:

const detail = { index: idx }
const event = new CustomEvent('slide', { bubbles: true, detail })
this.container.dispatchEvent(event)
Copy the code

The above code online address 👉 the first version of the cast component

Now, let’s summarize the structure of the above basic method as follows:

  • The structure designHTML
  • Show the effectCSS
  • Behavior designJS, respectively containAPI (function)Event(Control flow)

Well, here, our rotation component is basically completed! However, can you look at the above code and see if there are any problems?

In the above code design, there are mainly the following problems:

  • Constructors are too complex
  • Scalability and flexibility are relatively low

(3) Reconstruction of multicast components: plug-in

The above code is obviously not good enough, so what would you do if you were to refactor the wheel map component?

Now, let’s first decouple this component. By decoupling, we mean:

  • Extract control elements into plug-ins;
  • Plug-ins and components are linked by dependency injection.

As shown below:

From the image above, we can see that the three control elements are extracted to form the plug-in by scrolling left and right and clicking the toggle button at the bottom.

The three removed plug-ins are encapsulated separately, and the encapsulation results are as follows:

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); }}// Encapsulate the controller function
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'; }); }}// Encapsulate the left slide function
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(); }); }}// Encapsulate the right slider function
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();
Copy the code

The above code online url 👉 the first step in the decoupling of the cast component

(4) Reconstruction of multicast components: templatization

Above we initially plug-in the control elements. If we want to scroll four

  • elements, we need four
  • elements. If we want to scroll four < Li > elements, we also need to write 100
  • elements to control.
  • The answer is definitely no. Therefore, we continue to decouple the HTML by templating it to make it easier to extend. As shown below:

    We ended up making the component more extensible by binding an unfixed amount of content in a dynamic way. Attached code:

    HTML code:

    <div id="my-slider" class="slider-list"></div>
    Copy the code

    The CSS code:

    #my-slider{
      position: relative;
      width: 790px;
      height: 340px;
    }
    
    .slider-list ul{
      list-style-type:none;
      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__control{
      position: relative;
      display: table;
      background-color: rgba(255.255.255.0.5);
      padding: 5px;
      border-radius: 12px;
      bottom: 30px;
      margin: auto;
    }
    
    .slide-list__next..slide-list__previous{
      display: inline-block;
      position: absolute;
      top: 50%;
      margin-top: -25px;
      width: 30px;
      height:50px;
      text-align: center;
      font-size: 24px;
      line-height: 50px;
      overflow: hidden;
      border: none;
      background: transparent;
      color: white;
      background: rgba(0.0.0.0.2);
      cursor: pointer;
      opacity: 0;
      transition: opacity .5s;
    }
    
    .slide-list__previous {
      left: 0;
    }
    
    .slide-list__next {
      right: 0;
    }
    
    #my-slider:hover .slide-list__previous {
      opacity: 1;
    }
    
    
    #my-slider:hover .slide-list__next {
      opacity: 1;
    }
    
    .slide-list__previous:after {
      content: '<';
    }
    
    .slide-list__next:after {
      content: '>';
    }
    
    .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

    JS code:

    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>`;
      }
      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 `
          <div class="slide-list__control">
            ${images.map((image, i) => `
                <span class="slide-list__control-buttons${i===0?'--selected':' '}"></span>
             `).join(' ')}
          </div>    
        `.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 `<a class="slide-list__previous"></a>`;
      },
      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 `<a class="slide-list__next"></a>`;
      },
      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();
    Copy the code

    By removing the data content, does this make components more extendable?

    The above code online url 👉 the second step of the decoupling of the cast component

    (5) Multicast component reconstruction: component framework

    Finally, let’s abstract the framework of the entire component. By abstraction, common component models are abstracted out. As shown below:

    The JS code is attached below:

    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>`;
      }
      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 `
          <div class="slide-list__control">
            ${images.map((image, i) => `
                <span class="slide-list__control-buttons${i===0?'--selected':' '}"></span>
             `).join(' ')}
          </div>    
        `.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 `<a class="slide-list__previous"></a>`;
      },
      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 `<a class="slide-list__next"></a>`;
      },
      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();
    Copy the code

    As you can see, we first abstracted the Component and then asked the Slider to inherit the Component to better separate the logic out.

    (6) Summary

    Now, let’s summarize the series of refactorings performed by the entire component above. Details are as follows: 👇

    • Principle of component design: encapsulation, correctness, extensibility, reuse;
    • Steps to realize components: structural design, display effect, behavior design;
    • Three refactorings: plug-in, template, and abstract (component framework).

    3. Process abstraction 👌

    (1) Definition

    • By abstraction, we mean some methods used to deal with local detail control.
    • Process abstraction is a thinking method as well as a programming paradigm.
    • Basic application of functional programming ideas.

    As shown below:

    From the figure above, we can abstract the procedure as the input and output of the function.

    (2) Illustration of examples

    Suppose we now want to make a to do list, as shown below:

    Now what we want to do is to click ✅ on the left and the current line will disappear. So at this time, many partners may operate like the following:

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

    However, since removeChild is triggered every time we click on it, the Dom node will disappear after removeChild is triggered for the first time. The following error occurs:

    Therefore, we offer the following solutions to solve this problem.

    (3) Once

    To enable the “once” requirement to cover different event handling, we can separate this requirement out. This process is called process abstraction. Let’s start with a picture:

    We can think of the process abstractly as opening the door, the act of opening the door as a process, and then after opening the door, we should put something outside in. Right?

    Once the outside stuff comes in, it abstracts the whole process out, and that’s process abstraction.

    So, for the code above, we’ll wrap a once function and apply it to the scenario above. The specific code is as follows:

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

    (4) Higher-order functions

    1) What is a higher-order function

    Based on the idea of process abstraction above, at present we derive it from higher-order functions.

    The so-called high-order function, that is, function as a parameter or function as a return value of a function. Alternatively, if you take both functions as arguments and functions as return values, this type is called a function decorator.

    Let’s look at some common higher-order functions.

    2) Commonly used higher-order functions

    • Once: A function that is executed only Once.
    • Throttle: Changes the execution from frequent to once in a while.
    • Denounce: Changes frequent execution to the last one.
    • Consumer: Used to consume data. (Added by Consumer)
    • Iterative: An iterator.

    The title of higher-order functions above has been put into the corresponding examples, friends can click on the corresponding to learn, here is no longer one by one examples.

    (5) Thinking and discussion

    Now let’s think about the question, why do we use higher-order functions?

    In fact, the more pure functions in a system, the better the maintainability of the system.

    A pure function is pure if its input value is unique and its output value is unique. Such as:

    function add(x, y) {
        return x + y;
    }
    add(1.2); / / 3
    Copy the code

    What about an impure function, where the input and output are not unique, then it’s not a pure function. Such as:

    let x = 10;
    function foo() {
        return x++;
    }
    function bar() {
        return x * 2;
    }
    
    foo();
    foo();
    bar();
    bar();
    Copy the code

    If we slightly adjust the order of the functions, they will all perform differently. This is called an impure function.

    The reason for using pure functions is that they make our code more maintainable and extensible.

    (6) Programming paradigm

    1) Imperative and declarative

    Putting the above higher-order functions together, we can summarize them as imperative and declarative. As shown below:

    In practice, we can say that javascript is both imperative and declarative.

    For imperative programming, it means telling the machine how to do something (how) so that whatever you want (WAHT) will be done on your command.

    Declarative programming, which tells the machine what you want and lets the machine figure out how, is more scalable.

    As shown below:

    2) Examples

    Here are a few examples for you to experience the effect in practical application

    👉Toggle – imperative

    👉Toggle – declarative

    👉 Toggle – three states

    The above three examples describe the effect of color switching in different states, you can click the link to view ~

    Third, 🎩 conclusion

    In this article, we explained the three principles of how to write JS well, namely, each role, component encapsulation, and process abstraction. I also learned about higher-order functions and programming paradigms, and some of their uses.

    Here, about writing good JS three principles to explain the end! Do not know whether everybody has a new understanding to JS again?

    If you think this article is helpful to you, you might as well like to support yo ~~😛

    🙂 previous recommendation

    👉 HTML basics worth watching

    👉 CSS is still just about writing layouts? Discover the wonders of CSS in 10 minutes!

    👉 [youth Training camp] – Is the front end just a cutout? Learn about UI design for developers