Javascript 编程原则 | 青训营

113 阅读4分钟

在《如何写好 JavaScript》课程中,月影老师主要讲述了关于Javascript编程的三大原则:各司其职、组件封装和过程抽象。本节课程主要讲述的是如何养成良好的开发习惯,锻炼敏锐的开发思维。课程比较偏向抽象和思考,听了之后能够帮助梳理Javascript开发的思路和拓展开发的思维,帮助我们将代码写的更加简洁优雅。

1. 各司其职

  1. HTML、CSS、JS应该各司其责
  2. 应当避免不必要的,由JS直接操作样式的操作
  3. 可以用class来表示状态
  4. 纯展示类交互寻求零JS方案

2. 组件封装

组件是指Web页面上抽出来一个个包含模版(HTML)、功能(JS)和样式(CSS)的单元。好的组件具备封装性、正确性、扩展性、复用性。

基本方法:结构设计、展示效果、行为设计(API(功能)、Event(控制流))

组件的改进空间:

  1. 解耦——插件化
  • 将控制元素抽取成插件
  • 插件与组件之间通过依赖注入方式建立联系
  1. 重构——模板化
  • 将HTML模板化,更易扩展
  1. 抽象——组件框架
  • 将组件通用模型抽象出来

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