如何写好JavaScript | 青训营笔记

127 阅读4分钟

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

本文主要记录课上一些重点内容,方便日后巩固和复习~

本堂课的重点内容:

  • 如何做到让HTML,CSS和JS的职能分离。

  • 以轮播图为例,展现了完整实现一个组件封装的过程。

  • 通过高阶函数的介绍,学会将过程抽象运用到实际代码中。

  • 对实际场景下的一些算法代码的思考,例如洗牌,分红包。

JS设计相关知识点:

职能分离

老师通过一个简单的深色浅色切换的例子,分别从三种不同写法出发,引出了职能分离的好处。

版本一

虽然实现了功能,但代码中有多处对CSS属性的控制,代码可读性较差,不利于后续扩展。

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

版本二

版本二相比一,将CSS属性抽取成类,可读性和扩展性更强,但JS在功能中的职能很低。

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

版本三

版本三将JS从功能中剥离,通过纯CSS实现切换。

深夜食堂(三)

收获知识点1、label标签可通过for属性控制input标签 2、通过CSS伪类可改变其兄弟结点的样式。

结论

shitang.png

组件封装

老师通过对组件的正确性扩展性复用性三方面重构代码,展现了组件封装的大致流程。

正确性

通过将轮播图抽象成类和API,实现轮播的基本操作,并通过自定义事件,将图片和圆点轮播解耦。

轮播图(一)

收获知识点:学会用自定义事件对组合功能解耦。

扩展性

原先代码的构造函数十分复杂,且大部分代码都是注册事件。我们可以将注册不同的事件以插件的形式依赖注入组件中,这样可以使代码扁平化,且扩展性高。

轮播图(二)

收获知识点将组件中的自定义事件抽取成插件,依赖注入组件。

复用性

在插件化重构的基础上,我们发现封装后的组件仍有大量的标签,且如果我们想要改变轮播的图片或取消圆点轮播等,需要去修改代码,不符合组件的复用性。

轮播图(三)

收获知识点可在组件封装中去操作DOM挂载,以适应组件属性的变化。

总结

这个组件目前的泛用性还有改进空间,比如轮播的可以是视频或其他组件等。我个人有两种思路:可以用 插槽机制或对每个轮播图再封装一个轮播项组件,提供子组件功能。

zujian.png

过程抽象

将某一过程需求剥离出来。

常用高阶函数

  • Once
//只执行一次,保证接口幂等性
function once(fn) {
  return function(...args) {
    if(fn) {
      const ret = fn.apply(this, args);
      fn = null;
      return ret;
    }
  }
}
  • Throttle
//也称节流阀,可限制网络请求
function throttle(fn, time = 500){
  let timer;
  return function(...args){
    if(timer == null){
      fn.apply(this,  args);
      timer = setTimeout(() => {
        timer = null;
      }, time)
    }
  }
}
  • Debounce
//防抖动,可用于自动保存
function debounce(fn, dur){
  dur = dur || 100;
  var timer;
  return function(){
    clearTimeout(timer);
    timer = setTimeout(() => {
      fn.apply(this, arguments);
    }, dur);
  }
}
  • Consumer / 2
//等待上一次fn执行,有点像MQ的消费者
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)
    }
  }
}
  • Iterative
//同时操作多个
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]);
  }
}

//对原fn装饰一层
const setColor = iterative((el, color) => {
  el.style.color = color;
});

//给hof传参
setColor(els, 'red');

收获知识点了解常用hof函数的使用,以及一般适用场景。

JS算法相关知识点:

评判代码好坏

代码的好坏不是三言两语就能确定的,取决于风格效率约定使用场景设计等多方面因素。

美观

我们不能从代码外观去判断代码好坏,应该基于实际场景而考虑。如下代码中对m矩阵的判断尽管不是很美观,但是满足较好渲染条件下的最优写法,时间复杂度较低。

sprite.png

效率

代码的效率也不是评判代码好坏的唯一标准,如下代码中尽管时间复杂度是O(n),但实际使用场景中,需要处理的字符串也不会太长,也能达到较好的效果。

leftpad.png

优化上述代码的最优思路是用快速幂实现的,以2次幂的方式补字符。

洗牌算法

算法思路:每次在i + 1个数里随机选,能够保证概率是均等的。

function shuffle(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]];
  }
  return c;
}

分红包 ── 分西瓜法

每次挑选数值最大的分。

分红包-切西瓜

分红包 ── 洗牌法

将红包数值扩大100倍,通过洗牌,对红包数值切分n - 1次,得到n个随机红包。

分红包-洗牌法