This is the third day of my participation in the August Text Challenge.More challenges in August

Note the outline

  1. Separation of responsibilities for HTML, CSS, and JS
  2. Component encapsulation, take the example of a multicast map
  3. Process abstraction, higher-order functions, and programming paradigms

Each in his own place

layered

HTML – > structure

CSS – > performance

Javascript – > behavior

Const BTN = document.getelementById ('modeBtn'); // const BTN = document.getelementById ('modeBtn'); btn.addEventListener('click',(e) => { const body = document.body; If (e. arget. InnerHTML = = = '🌞') {body. Style. BackgroundColor = 'black'; body.style.color = 'white'; E. arget. InnerHTML = '🌛'; } else { body.style.backgroundColor = 'white'; body.style.color = 'black'; E. arget. InnerHTML = '🌞'; }}); // Js changes the style, but it's not easy to understand what the whole thing is doingCopy the code
Const BTN = document.getelementById ('modeBtn'); // const BTN = document.getelementById ('modeBtn'); btn.addEventListener('click',(e) => { const body = document.body; if(body.className ! == 'night' ){ body.className = 'night'; }else { body.className = ''; }}); // Use a descriptive className to switch, js and CSS styles do their jobCopy the code
<body> <input ID ='modeCheckBox' type='checkbox'> <div class='content'> <header> <label ID ='modeBtn' For ='modeCheckBox'></label> < H1 ></ h1> </header> <main> </main> </div> </body> // pure CSS #modeCheckBox: Checked + .content { background-color:black; color: white; transition: all 1s; } // The state cannot be saved because it does not use jsCopy the code
  • Avoid JS modifying DOM styles directly
  • This can be changed using className
  • Pure presentation class interaction seeks zero JS scheme

02. Component encapsulation

Components are extracted units of a Web page that contain templates, functionality, and styles. Good components should be encapsulation, correctness, extensibility, and reuse.

Example with native JS to write an e-commerce site of the wheel broadcast map

<div id='my-slider' class='slider-list'>
	<ul>
  	<li class='slider-list__item--selected'><img src='1.img'/></li>
    <li class='slider-list__item'><img src='2.img'/></li>
    <li class='slider-list__item'><img src='3.img'/></li>
    <li class='slider-list__item'><img src='4.img'/></li>
  </ul>
</div>
Copy the code
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; }Copy the code

API design should ensure atomic operation, single responsibility and flexibility.

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
  }
  getSelectdItemIndex(){
  	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 = getSelectdItemIndex();
    const nextIdx = (currentIdx+1)%this.item.length;
    this.slideTo(nextIdx)
  }
  slidePrevious(){
    const currentIdx = getSelectdItemIndex();
    const previousIdx = (currentIdx+1)%this.item.length;
    this.slideTo(previousIdx)
  }
}
Copy the code

Event control flow: Use custom events to decouple

The decoupling

  • Extract control elements into plug-ins
  • Dependency injection is used between components and plug-ins
  • Template HTML to make it easier to expand
  • Abstract common component models (component frameworks)
//https://code.h5jun.com/weru/3/edit?html,css,js,output 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(); //js is decoupled but HTML is notCopy the code

Template HTML to make it easier to extend

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

03. Process abstraction

  • Methods used to handle local detail control
  • Basic application of functional programming ideas

Higher order function HOF

Higher-order functions are commonly used

  • Take functions as arguments
  • Take a function as the return value
  • Often used as a function decorator

Once

Operation limit

  • Some asynchronous operations
  • A one-time HTTP request

To enable a requirement that is executed only once to override different event handling, we can separate this requirement out. This process is called process abstraction

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

Throttle

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

Debunce

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

Consumer

Iterative

Pure functions

  1. No side effects
  2. Idempotent (called at any time, the result is fixed)

Non-invasive code

Programming paradigm

Imperative and declarative

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

Unified style: TAB, semicolon, ESLint, team convention

Execution efficiency: Combined with usage scenarios

Shuffle algorithm

,1,2,3,4,5,6,7,8,9 const card = [0]; function draw(cards){ const c = [...cards]; for(let i = c.length; i>0; i--){ const pIdx = Math.floor(Math.random()*i); [c[pIdx],c[i-1]] = [c[i-1],c[pIdx]]; yield c[i-1]; } } const result =draw(cards); console.log([...result]);Copy the code

Bonus package algorithm

  1. Cut the watermelon method, each time cut the largest or second largest red envelope
  2. Shuffle cards, 10 red envelopes to find 9 positions

Afterword.

This note is also quite long. I reviewed the content of Teacher Yueying’s class again and learned a lot of powerful things, including component encapsulation and higher-order functions, which impressed me deeply. As the title of the teacher’s class is “How to write JS well”, the content of this class is mostly to teach how to abstract and think when writing JS code. Responsibility assignment, component encapsulation, process abstraction, programming paradigms, algorithms and so on, only to be slowly thought and practice in the learning process of coding. To be continued…