JS学习记录 | 青训营

40 阅读5分钟

JS学习记录 | 青训营


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


如何写好JavaScript

遵循一些原则:

  • 各司其职 -------------->HTML,CSS和JavaScript职能分离
  • 组件封装 -------------->UI组件的正确性、拓展性、复用性
  • 过程抽象 -------------->函数式编程思想

各司其职

应当避免由JS直接操作样式 当只有纯展示类交互时,应当避免用js来直接操作样式。
使用HTML和CSS的伪类有时候也可以达到相同的效果。

当使用JS进行直接操作样式:
如切换白天或者夜间模式,可以使用JS控制dom API达到。这种方式相当于是用JS控制CSS。
如果项目换个人维护,未必就明白这段代码是做什么的。


操作HTML状态,符合各司其职
使用不同的class进行切换。这种方式很容易理解。
采用不同的className能够直观的展示这段代码的作用。


使用HTML或者CSS进行控制
如:白天和夜间模式切换
使用CSS伪类选择器和HTML标签
给label标签加一个for属性,内容是一个checkbox的id,
将checkbox设置为display:none。隐藏这个checkbox。
使用伪类和选择器进行改变样式

组件封装

当一个组件由HTML,CSS,JS设计出来的时,如果后续要修改这个组件显示在网页上的情况,可能需要修改HTNL,CSS,JS的代码。当产品需求不断改变时,重复性的修改这些代码容易出错。所以需要将组件中的内容插件化。最后将插件和组件之间通过依赖注入的形式表现出来。

简单描述:将原来的代码进行重构,对内容进行解耦。将控制元素抽取成插件。将插件与组件之间通过依赖注入的方式建立联系。

将HTNL模板化

以轮播图为例,当需要修改显示的图片的时候,不应该去修改HTNL代码,而是通过修改数据从而使得轮播图上的图片被修改。这时就需要将HTML模板化,根据数据渲染出想要的效果。

使用到render()方法,通过将数据传到render从而渲染出对应的组件,此时引用的HTML代码只需要一行。

重构后的代码能够很方便的修改需要使用的显示内容,也能够方便添加新的功能。

抽象

将组件用通用模型抽象出来,抽象出简单的组件框架。


后续还能够对CSS进行模板化,这样就能够使得只修改数据就能达到效果。

过程抽象

是函数式编程的基本应用。

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

用高阶函数完成过程抽象

HOF

以函数作为参数,用函数作为返回值,常用于函数装饰器

等价高阶函数 ------------>接收的参数函数和返回的参数函数是一致的

  • Once()

当需要点击请求只能请求一次时,可以添加{once: true}参数
如:

function once(fn) {
  return function(...args) {
    if (fn) {
      const ret = fn.apply(this, args);
      fn = null;
      return ret;
    }
  }
}
const list = document.qwerySelector('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);
  }, {
    once: true
  });
})

只请求一次数据库,使用once()将过程封装。

const list = document.qwerySelector('ul');
const buttons = list.querySelectorAll('button');
buttons.forEach((button) => {
  button.addEventListener('click', once((evt) => {
    const target = evt.target;
    target.parentNode.className = "completed";
    setTimeout(
      () => {
        list.renoveChild(target.parentNode);
      }, 2000
    );
  })
)})

这样只会对数据库进行一次请求。 在once中,第一次调用if(fn)后,fn会被清空成null,那么之后就不会被调用if。从而保证了只调用一次。

  • Throttle

节流函数,鼠标移动或者滚动,触发频率高,会带来一定的性能开销。可以通过该函数设定频率。

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

只有在超时的时候才会继续调用。

  • dbounce

防抖函数

理解:例如:自动保存,在编辑过程中不自动保存,在编辑完成后若干毫秒开启自动保存。

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

当调用该函数时,一直清空timer的值,当不再调用函数的时候,调用fn.apply()

  • Comsumer/2

"累计" 例如:鼠标点击事件,当不断点击时,记录点击次数,然后每隔若干秒进行一次累加。最后耗尽点击次数为止。

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

判断subject是否是可迭代元素,用每个元素调用fn方法。

测试正确性
使用一个加法就能够测试它的正确性

function add(x, y){
  return x + y
}
const addMany = iterative(add);
console.log(addMany([1, 2, 3, 4], 5));  //得到6,7,8,9

纯函数

无状态的函数,任何a输入都达到b 没有副作用,可预期

function add(x, y){
  return x + y;
}

非纯函数

let idx = 0;
function count(){
  return idx ++;
}

纯函数的好处是任何时候都能进行测试,不需要配置测试环境就能得到想要的值。
非纯函数测试时需要建立特定的外部环境。
非纯函数越多,可维护性越差

可以使用iterative高阶函数将非纯函数转化为纯函数。

const xxx = iterative(xxxx函数)

编程范式

命令式与声明式

JavaScript两者都有。
命令式:面向过程,面向对象
更强调怎么做。

let list = [2, 3, 4, 5];
let chg = []
for(let i = 0;i < list.length;i ++){
  chg.push(list[i] * 2);
}

声明式: 逻辑思考函数式

let list = [2, 3, 4, 5];
const double = x => x * 2;
list.map(double);

声明式有时候可能需要写一个新函数,没有命令式来的方便。
当业务逻辑拓展的时候,采用命令式也许会大大增加代码行数。使得代码过于冗长。而使用声明式只需要添加少数代码完成。

引用参考:

JavaScript 编码原则之各司其责 - 掘金 (juejin.cn)
JavaScript 编码原则之组件封装 - 掘金 (juejin.cn)
JavaScript 编码原则之过程抽象 - 掘金 (juejin.cn)