在《如何写好 JavaScript》课程中,月影老师主要讲述了关于Javascript编程的三大原则:各司其职、组件封装和过程抽象。本节课程主要讲述的是如何养成良好的开发习惯,锻炼敏锐的开发思维。课程比较偏向抽象和思考,听了之后能够帮助梳理Javascript开发的思路和拓展开发的思维,帮助我们将代码写的更加简洁优雅。
1. 各司其职
- HTML、CSS、JS应该各司其责
- 应当避免不必要的,由JS直接操作样式的操作
- 可以用class来表示状态
- 纯展示类交互寻求零JS方案
2. 组件封装
组件是指Web页面上抽出来一个个包含模版(HTML)、功能(JS)和样式(CSS)的单元。好的组件具备封装性、正确性、扩展性、复用性。
基本方法:结构设计、展示效果、行为设计(API(功能)、Event(控制流))
组件的改进空间:
- 解耦——插件化
- 将控制元素抽取成插件
- 插件与组件之间通过依赖注入方式建立联系
- 重构——模板化
- 将HTML模板化,更易扩展
- 抽象——组件框架
- 将组件通用模型抽象出来
3. 过程抽象
用来处理局部细节控制的一些方法函数式编程思想的基础应用
下述关于纯函数和非纯函数的定义来源于:《谈JavaScript中纯函数与非纯函数》
为了能够让只执行一次的需求覆盖不同的事件处理,我们可以将这个需求剥离出来。这个过程我们称为过程抽象
1. 纯函数 和 非纯函数
纯函数
定义:当给定相同的输入时,纯函数总是返回一致的输出,并且永远不会产生超出函数范围的效果,使它们可以预测。纯函数不管执行多少次,结果都是可预测的。
非纯函数
定义:当给定相同的输入时,非纯函数可能不会返回一致的结果,并且它们可能会产生超出函数范围的影响,因此非纯函数的测试需要营造特定环境才能进行。
非纯函数是不可预测的,有副作用的。当一个项目中,非纯函数的数量越多,项目可维护性越差。
而高阶函数的出现,使得非纯函数可以简化成高阶函数+纯函数的形式进行使用。
同时,因为高阶函数也是纯函数,这样的调用形式不仅降低了测试成本还提高了项目可维护性。
2. HOF (高阶函数)
高阶函数具有以函数作为参数||以函数作为返回值的特点。
当一个函数以函数为参数的同时也以函数为返回值,那么这个函数被称为函数装饰器,如下:
function HOF0(fn) {
return function(...args){
return fn.apply(this,args);
}
}
2. 常用高阶函数
- Once
功能: 使得函数只执行一次
function once(fn){
return function(...args){
if(fn){
const ret = fn.apply(this,args);
fn = null;
return ret;
}
}
}
- throttle(节流)
功能: 让函数在x秒内只执行一次
```javascript
function throttle(fn,time= 500){
let timer;
return function(...args){
if(timer == null){
fn.apply(this,args);
timer = setTimeout{() => {
timer = null;
}, time)
}
}
}
```
- debounce
功能:让函数在x秒内的执行结果取决于最后一次调用的条件。
```javascript
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)
}
}
}
- interactive
功能:实现批量操作;
interactive会对输入参数进行可迭代判断,若是不可迭代的则只调用一次就返回;若是可迭代的则不断进行迭代批量操作。
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]);
}
}
3. 编程范式
现代化的编程语言支持命令式(面向过程和面向对象)和声明式(逻辑式和函数式)的代码风格。而Javascript同时支持命令式和声明式的代码风格。
命令式更偏向于怎么做,而声明式更倾向于做什么。
以按钮状态转化为例:
-
命令式的代码如下:
switcher.onclick = function(evt){ if(evt.target.className === 'on'){ evt.target.className='off'; }else{ evt.target.className= 'on' ; } } -
声明式的代码如下:
需要先抽象过程,定义一个叫toggle的高阶函数,并设置切换的状态:
function toggle(...actions){
return function(...args){
let action = actions.shift();
actions.push(action);
return action. apply(this, args);
}
}
switcher.onclick = toggle(
evt => evt.target.classNam = 'off'
evt => evt.target.classNam = 'on'
);
虽然上述例子中声明式的代码看着更加复杂,但由于声明式的代码编写往往比命令式的代码更具有扩展性,在实际应用中声明式的代码风格更具优势。
例如,在上述代码的基础上给按钮再添加一个状态’warm‘:
-
命令式的代码就要多加上1个判断逻辑:
switcher.onclick = function(evt){ if(evt.target.className === 'on'){ evt.target.className='warm'; } else if(evt.target.className === 'warm'){ evt.target.className='off'; } else{ evt.target.className= 'on' ; } } -
而声明式的代码只需要再加上一个状态即可:
function toggle(...actions){ return function(...args){ let action = actions.shift(); actions.push(action); return action. apply(this, args); } } switcher.onclick = toggle( evt => evt.target.classNam = 'off' evt => evt.target.classNam = 'on' evt => evt.target.classNam = 'warm' );