JavaScript | 青训营笔记

67 阅读3分钟

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

一、重点内容:

  • 写好JS的三大原则:各司其职,组件封装,过程抽象
  • 写代码的依据
  • 常见问题的几种算法
  • 写JS的逻辑与思维

二、详细知识点介绍:

学习书籍

犀牛书和红宝书

写好JS的一些原则

各司其职:HTML、CSS、JavaScript职能分离

组件封装:好的UI组件具有正确性、扩展性、复用性

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

例子

如何切换浅色和深色模式

直接操作CSS

直接操作HTML

纯CSS:checkBox,for,伪类,相邻选择器

轮播图组件

结构设计:编写HTML

展现效果:CSS

行为设计:新建类,添加API和Event控制流,使用自定义事件来解耦。

重构:插件化

将控制元素抽取成插件,插件与组件之间通过依赖注入方式建立联系

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

重构:模板化

将HTML模板化,更易于扩展

类似React的render

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>`;
    }
    ...
}

组件框架:抽象化

将组件通用模型抽象出来

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

过程抽象

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

高阶函数

  • 以函数作为参数
  • 以函数作为返回值
  • 常用于作为函数装饰器
function HOF0(fn) {
    return function(...args) {
        return fn.apply(this, args);
    }
}

常用高阶函数:

  • 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 延时
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];
const double = x => x * 2;
list.map(double);

写代码需要关注什么?

使用场景

快速幂

while (n) {
    if (n % 2 == 1) {
        result += string;
    }
    if (n > 1) {
        string += string;
    }
    n >>= 1;
}

切换交通灯

数据抽象,状态数组

过程抽象

异步+函数式

判断4的幂

常规操作:

  • 从1循环幂,判断是否相等;
  • 位运算,不断右移2位后,判断是否等于1;

还有两个NB操作:

  • 位运算的巧妙应用:num & (num - 1)会使二进制数的1少一个;4的幂的1会在奇数位
  • 把数字转为二进制字符串,使用正则匹配
function isPowerOfFour(num){
  num = parseInt(num);
  
  return num > 0 &&
         (num & (num - 1)) === 0 &&
         (num & 0xAAAAAAAAAAAAA) === 0;
}

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

tips:js的整数位最多53位

洗牌

正确写法:先抽出一张放在上面

const cards = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9];

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

洗牌生成器

const cards = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9];

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

分红包

  • 切西瓜法:选一块最大来切
  • 栅栏法:将红包看为数轴,随机将其插入一道栅栏,进行切分

三、课后个人总结:

今天的课程干货满满,十分实用,月影老师不仅教授了JS的三大原则,还为我们一一举例,使我们的理解更加深刻,在以后实践的过程中还能参照着前进。下午月影老师在解决常见问题的过程中,将编写JS的逻辑与思维一并传授,受益匪浅。