如何写好JavaScript | 青训营笔记

86 阅读5分钟

这是我参与「第四届青训营 」笔记创作活动的第1天

写好JS的一些原则

各司其责

  • 概念:各司其责就是就是让html,css,js的职责分离开来
  • 案例:以网页深色浅色模式作为案例 先看第一段实现代码
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 = '🌞';
    }
  });

以上代码通过操作dom元素来修改样式,也就是用js来操作了css样式,看起来没问题,用起来也没问题,接下来让我们看第二段代码

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

没有对比就没有伤害,第二段代码对比第一段,能够发现第二个代码则是定义了一个class,通过添加和删除class来修改其样式。接下来让我们看看第三个版本

//此处是html代码



 <input id="modeCheckBox" type="checkbox">
  <div class="content">
    <header>
      <label id="modeBtn" for="modeCheckBox"></label>
      <h1>深夜食堂</h1>
    </header>
    <main>
      <div class="pic">
        <img src="https://p2.ssl.qhimg.com/t0120cc20854dc91c1e.jpg">
      </div>
      <div class="description">
        <p>
            这是一间营业时间从午夜十二点到早上七点的特殊食堂。这里的老板,不太爱说话,却总叫人吃得热泪盈
            眶。在这里,自卑的舞蹈演员偶遇隐退多年舞界前辈,前辈不惜讲述自己不堪回首的经历不断鼓舞年轻人,最终令其重拾自信;轻言绝交的闺蜜因为吃到共同喜爱的美食,回忆起从前的友谊,重归于好;乐观的绝症患者遇到同命相连的女孩,两人相爱并相互给予力量,陪伴彼此完美地走过了最后一程;一味追求事业成功的白领,在这里结交了真正暖心的朋友,发现真情比成功更有意义。食物、故事、真情,汇聚了整部剧的主题,教会人们坦然面对得失,对生活充满期许和热情。每一个故事背后都饱含深情,情节跌宕起伏,令人流连忘返 [6]  。
        </p>
      </div>
    </main>
  </div>
  
  
  
  
  
  //此处是css代码
  ```
#modeCheckBox {
    display: none;
  }

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

用第三段对比前两个版本的代码,能够发现,版本3通过了纯html+css实现了该功能,没有用到js。那么第三个版本是如何实现的,其实是通过伪类选择器实现,通过CheckBox来修改样式。

组件封装

  • 概念:好的UI组件应该具备正确性、扩展性、复用性。

  • 基本方法

    • 结构设计
    • 展现效果
    • 行为设计
      • API
      • Event
  • 重构:插件化

    • 将控制元素抽取成插件,插件组件之间通过依赖注入建立联系
  • 重构:模板化

    • 将HTML模板化,更易于扩展

QQ图片20220728094049.png

  • 组件框架
    • 将组件通用模型抽象出来

t0152a5c963a9854531qqqq.png

  • 总结:
    • 组件设计的原则:封装性、正确性、扩展性、复用性
    • 实现组件的步骤:结构设计、展现效果、行为设计
    • 三次重构
      1. 插件化
      2. 模板化
      3. 抽象化(组件框架)

过程抽象

  • 概念:应用函数式编程思想。
  • 用来处理局部细节控制的一些方法
  • 函数式编程思想的基础应用

高阶函数

Once

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

function once(fn) {
//outer scope closure
    return function(...args) {
    //inner scope
      if(fn) {
        const ret = fn.apply(this, args);
        fn = null;
        return ret;
      }
    }
  }

HOF

  • 以函数作为参数
  • 以函数作为返回值
  • 常用于作为函数装饰器
function HOF0(fn) {
//默认的等价高阶函数,一般的高阶函数都是在该基础上做修改
    return function(...args) {
      return fn.apply(this, args);
    }
  }
 

常用的高阶函数

  • Once
  • Throttle
    • 节流函数
    • 使用场景:一些mousemove鼠标跟随事件,触发频率过高,而我们不需要这么高的频率,就可以使用节流函数。
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);
})
  • Debounced
    • 防抖
    • 使用场景:适用于一些需要实时保存的需求,比如键盘输入内容并保存,每次保存会对服务器造成负担,需要等键盘敲击停下,内容不再变化的时候在进行保存
    • 案例:小鸟根据鼠标移动,不会一直追踪,只有等到鼠标停止才会追踪上去
var i = 0;
setInterval(function(){
  bird.className = "sprite " + 'bird' + ((i++) % 3);
}, 1000/10);

function debounce(fn, dur){
  dur = dur || 100;
  var timer;
  return function(){
      //每次触发函数都会清除clearTimeout,所以如果一直不到时间,会一直清除
    clearTimeout(timer);
    timer = setTimeout(() => {
      fn.apply(this, arguments);
    }, dur);
  }
}

document.addEventListener('mousemove', debounce(function(evt){
  var x = evt.clientX,
      y = evt.clientY,
      x0 = bird.offsetLeft,
      y0 = bird.offsetTop;
  
  console.log(x, y);
  
  var a1 = new Animator(1000, function(ep){
    bird.style.top = y0 + ep * (y - y0) + 'px';
    bird.style.left = x0 + ep * (x - x0) + 'px';
  }, p => p * p);
  
  a1.animate();
}, 100));
  • Consumer
    • 将同步调用改成异步调用
    • 使用场景:在游戏里面连续快速点击,使其每隔一段时间改变一次数据
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(ref, x){
  const v = ref.value + x;
  console.log(`${ref.value} + ${x} = ${v}`);
  ref.value = v;
  return ref;
}

let consumerAdd = consumer(add, 1000);

const ref = {value: 0};
for(let i = 0; i < 10; i++){
  consumerAdd(ref, i);
}
  • Iterative
    • 批量操作
const isIterable = obj => obj != null 
  && typeof obj[Symbol.iterator] === 'function';

function iterative(fn) {
  return function(subject, ...rest) {
    if(isIterable(subject)) {
      const ret = [];
      for(let obj of subject) {
        ret.push(fn.apply(this, [obj, ...rest]));
      }
      return ret;
    }
    return fn.apply(this, [subject, ...rest]);
  }
}

const setColor = iterative((el, color) => {
  el.style.color = color;
});

const els = document.querySelectorAll('li:nth-child(2n+1)');
setColor(els, 'red');
  • 为什么要使用高阶函数:
    • 纯函数:返回值只由它调用时的参数决定,结果可预期,无副作用
    • 高阶函数一般都是纯函数
    • 尽量用纯函数,减少非纯函数的使用

编程范式

命令式与声明式

  • 命令式(更倾向于怎么做)
let list = [1, 2, 3, 4];
  let mapl = [];
  for(let i = 0; i < list.length; i++) {
    mapl.push(list[i] * 2);
  }
  • 声明式(更倾向于做什么)
let list = [1, 2, 3, 4];
  let mapl = [];
  for(let i = 0; i < list.length; i++) {
    mapl.push(list[i] * 2);
  }

案例

实现on和off的切换

  • 命令式
switcher.onclick = function(evt){
  if(evt.target.className === 'on'){
    evt.target.className = 'off';
  }else{
    evt.target.className = 'on';
  }
}
  • 声明式
//JS代码

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




//CSS代码

#switcher {
  display: inline-block;
  background-color: black;
  width: 50px;
  height: 50px;
  line-height: 50px;
  border-radius: 50%;
  text-align: center;
  cursor: pointer;
}

#switcher.on {
  background-color: green;
}

#switcher.off {
  background-color: red;
}

#switcher.on:after {
  content: 'on';
  color: white;
}

#switcher.off:after {
  content: 'off';
  color: white;
}
  • 优缺点:当切换状态比较多的时候,命令式需要添加分支,而声明式只需要添加状态,声明式比命令式有更好的扩展性