跟着月影学 Java Script | 青训营笔记

34 阅读3分钟

这是我参与「第五届青训营 」伴学笔记创作活动的第 2 天

以上为今日课上笔记内容

一.如何写好 JS

写好JS 的一些原则

1.各司其责: 让HTML、CSS和JavaScript职能分离。

2.组件封装: 好的UI组件具备正确性、扩展性、复用性。

3.过程抽象: 应用函数式编程思想。 应用函数式编程思想。

1.各司其责

HTML负责结构 CSS负责表象 JavaScript负责行为

09fb6c3d4515f5a16ef8922aed96123.png

例子

写一段JS,控制一个网页,让它支持浅色和深色两种浏览模式。如果是你来实现,你会怎么做?

453203d434c8230a1e6b8af40424ab8.png

版本一:操作body.style

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 ='太阳';
}
});

版本二:操作body.className //比一更能展现各司其职

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

版本三:

<body>
    <input id="modeCheckBox” type="checkbox">
    <div class="content">
       <header>
          <label id="modeBtn” for="modeCheckBox"></label>
          <h1>深夜食堂</h1>
       </header>
       <main>
       ...
      </main>
    </div>
</body>

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

结论:

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

2.组件封装

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

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

654ef5696bd3f94ebc21e918b79de7d.png

  • 结构设计:HTML

结构:HTML

轮播图是一个典型的列表结构我们可以使用无序列表ul元素来实现。

<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/t0ladbe3351db853eb3.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.com/t01331ac159b58f5478.jpg"/>
      </li>
   </ul>
</div>
  • 展现效果: CSS

表现: CSS

①使用 CSS 绝对定位将图片重叠在同一个位置

②轮播图切换的状态使用修饰符 (modifier)

③轮播图的切换动画使用 CSS 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 ls; 
opacity: 0;
text-align: center;
}

.slider-list item--selected{ 
transition:opacity 1s;
opacity: 1;
}
  • 行为设计: API

行为: JS

满足灵活性API 设计应保证原子操作,职责单一

Slider

+getSelectedltem()

+getSelectedltemIndex()

+slideTo()

+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).index0f(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);
}

行为设计:控制流(HTML)

行为:控制流

使用自定义事件来解耦

<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>

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

总结:

基本方法

①结构设计

②展现效果行为设计

③API (功能)

④Event(控制流)

改进

①重构:插件化

解耦

将控制元素抽取成插件

插件与组件之间通过依赖注入方式建立联系

function pluginController(subject){
//...
}

function pluginNext(subject){
//...
}

function pluginPrevious(subject){
//...
}

②解耦

将HTML模板化,更易于扩展

359a16e9374fabadbbbd52089e60af0.png

class Slider{
   constructor(id, opts ={images:[],cycle:3000}){
   ...
}
render(){
   const images = this.options.images; 
   const content =images.map(image =>
     <li class="slider-listitem">
        < img src="${image}"/>
     </li>
      `.trim());
    return `<ul>${content.join( '')}</ul>`;
   }
   ...
}

③重构:组件框架

抽象

将通用的组件模型抽象出来

f41423a7318257ca13948400e6bccb9.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');     
    pluginC  ontainer.className=`.${name}__plugin`;
    pluginContainer.innerHTML =plugin.render(this.options.data);
    this.container.appendChild(pluginContainer);
    
    plugin.action(this);});
  });
}
render(data){
   /* abstract */
   return''
   }
}

总结

①组件设计的原则:封装性、正确性、扩展性、复用性

②实现组件的步骤:结构设计、展现效果、行为设计

③三次重构

插件化

模板化

抽象化 (组件框架)

3.过程抽象

过程抽象用来处理局部细节控制的一些方法函数式编程思想的基础应用

f667cb558aaafd9e14c9837fd01e971.png

例子

操作次数限制

一些异步交互一次性的HTTP请求

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

1a9f3dbe58259637215e5ff3e79dd26.png

Once

为了能够让“只执行一次“的需求覆盖不同的事件处理,我们可以将这个需求剥离出来这个过程我们称为过程抽象

7bd4248987fdb508846a31dd4af1a25.png

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

高阶函数

①以函数作为参数

②以函数作为返回值

③常用于作为函数装饰器

常用高阶函数Once

①Throttle 一次 ②Debounce 节流,防抖 ③Consumer /2 每隔一段时间调用,延时调用 ④Iterative 循环调用 08df78414ffcc7d5ddb688b1ac8e41d.png

function HOFo( fn) {
   return function( ...args ) {
      return fn.apply( this, args );
   }
}

思考与讨论

2a09077eaf5022ab453827c7d328c51.png

编程范式

命令式与声明式

cbb9a5432b4d02cdbdeed00174f59a8.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,23, 4];
const double = x => X * 2;
list.map(double);

例子

Toggle - 命令式

Toggle - 声明式

Toggle - 三态

5dbc101a9cbf58b38d392146bb1fd4a.png

总结

①过程抽象 / HOF /装饰器

②命令式 / 声明式

二.Leftpad 这个代码好不好

//判断一个mat2d矩阵是否是单位矩阵

function isUnitMatrix2d(m) {
return m[0] === 1 & m[l] === 0 && m2) === 0 && m[3] === 1 && m4] === 0 && m[5] === 0:
}

那放在这里呢?

get layerTransformInvert(){
   if(this[_layerTransformInvert]) return this[_layerTransformInvert]; 
   const m =this.transformMatrix;
   if(m[0]===1&&m[1] === 0 &&m[2]=0 && m[3] === 1 && m[4] === 0 && m[5] === 0) {
   return null;
}
this[_layerTransformInvert] = mat2d.invert(m); 
return this[_layerTransformInvert];
}
  • 写代码最应关注什么?

风格约定

效率

使用场景

设计

  • 当年的 Left-pad 事件

事件本身的槽点

NPM 模块粒度

代码风格

代码质量/效率

原本代码

function leftpad (str, len, ch) {
   str = String(str);
   var i = -l;
   if (!ch && ch !== 0) ch = " "
   len = len - str.length;
   while (++i < len) {
   str = ch + str;
   }
return str;
}

重构代码

代码更简洁 效率提升

function leftpad (str, len, ch) {
str = String(str);
var i = -l;
if (!ch && ch !== 0) ch = " "
len = len - str.length;
while (++i < len) {
str = ch + str;
}
return str;
}

性能更好

adfa9dd7e5895de2105cd6f90dcd946.png

for (;;){
if ((count & 1) == 1) {
rpt += str;
}
count >>>=1if (count == 0) {break;
}
str += str;
}

性能的再次优化

c93cb844b7cb8495f09375d1da341d8.png

var rpt = “ ”;
do {
rpt += str;
str += str;
count &= count - 1;
} while(count);

例子

交通灯:状态切换

实现一个切换多个交通灯状态切换的功能

版本一:

af02f5e4344b16aab7ec34139e2cd33.png

版本二:

4142c67b7767e47d8c34c8dcaf11917.png

4a4d7c21de946c14546264928c21b97.png

版本三:(过度抽象)

7e2aca81b204134ad27089c966b9190.png

757518ebb676452dfb45cf4ec459cfb.png

版本四:(异步+函数式)

7a63d9d17752715308685dd9e02dd68.png

72b38d40bea77c5e917570da65c11c0.png

例子:判断是否为4的幂

版本一

function isPower0fFour(num){ 
num = parseInt(num);
while(num > 1) {
if(num % 4) return false; 
num /= 4;
}
return true;
}

版本二

function isPower0fFour(num){
num = parseInt(num);
while(num > 1) {
if(num & 0bl1) return false; 
num >>>= 2;
}
return true;
}

版本三

a&&(a-1) 会使二进制中的1少一个

4的幂和2的幂里二进制只有一个1

function isPower0fFour(num){ num = parseInt(num);
{
return num >0 &&
(num &(num-1))=== 0 &&(num & 0XAAAAAAAA)=== 0;
}

版本四

function isPower0fFour(num){
num = parseInt(num).toString(2);
return /^1(?:00)*$/.test(num);
}

例子: 洗牌 版本一:(错误写法) 46dc57c5993576f772a1eb0c9a5ede9.png

版本二:(数学归纳法)

14051d8e8e4fc06b2ba7e1e267bc669.png

版本三:(生成器)

79d7acb83cf78b5a8249dbcb2497262.png

洗牌思路

3099094dee225fa18bace901689ee5b.png

版本四:

6f5d60ebe4a4039a35845a0ff79476d.png

例子:分红包

7b5da8483633240decd38f6cb39dbb2.png

版本一:(切西瓜法)

631dc5797da70814a267ee4d24ec788.png

389aee2d40d45448478cd87c745b64e.png

版本二:(抽牌法)

1e4edcb5085898e6f8bb01e9898b9dd.png

7bac2d02e8dd3dd8077367c9a24b929.png