最近写JS的时候总是发现写出来的东西结构不够清晰,就想着提高一下对JS的重构能力,看了一些这方面的资料,总结了一些我认为比较重要的方法,后续发现有更多的实用方案会陆续加进来
UI层的松耦合
这里不必多说,就是HTML、CSS、JS需要做分层
- CSS中不能出现JS,也即不要使用CSS表达式
- 从JS中抽离CSS
除了要对元素做定位,可以在JS中使用style.top
、style.left
、style.right
和style.bottom
来对元素做定位
在其他情况下修改元素样式,最好是用JS操作CSS的className,比如:
1 | //bad |
这样在页面的生存周期中,JS可以随意添加和删除元素的className,由于className的样式都定义在CSS里,所以需要修改样式可以直接在CSS里修改,而不必修改JS,这样就实现了JS和CSS的松耦合。
- HTML中不要嵌入JS
经典的bad写法
1 | <button onclick="doSomething()" id="btn">click</button> |
当然这个是在很久之前大量流行的写法,现在基本大家都不会这么写了,都会在一个单独的JS文件里做绑定事件等工作了
- JS中不要出现大量的HTML
这种情况经常发生在给innerHTML赋值,比如
1 | var div = document.getElementById('div'); |
这样会造成调试的复杂性,不容易追踪bug,因此需要将HTML从JS中抽离
推荐使用类似handlebars的JS模板引擎
避免使用全局变量
过多的全局变量可能会造成命名冲突,造成代码脆弱不易维护。同时,全局环境是用来定义JS内置对象的地方,若在这个作用域中添加了自己定义的变量,可能会由读取浏览器自带的内置变量的风险
要解决上述问题推荐使用模块化编程
AMD异步模块定义,通过制定模块名称、依赖和一个工厂方法,依赖加载完成后执行这个工厂方法。
1 | define('module-name', ['dependency1', 'dependency2'], function(dependency1, dependency2) { |
使用模块加载器来使用AMD模块,常用的requirejs,它使用全局函数require()
来加载指定的依赖并执行回调函数,比如:
1 | require(['module-name'], function(name) { |
事件处理
很多事件处理的相关代码和事件的环境紧紧耦合,导致维护性极差
对于事件处理的优化主要有两个规则:1、隔离应用逻辑 2、不要分发事件对象
通过一个例子的改进来说明上述两点
1 | //bad |
上面代码是有问题的
首先,事件处理程序包含了应用逻辑,也就是包含了跟应用相关的功能性代码,而不是和用户行为相关的。
上面的代码是要在点击时在一个特定位置显示一个弹出框,这个弹出框的class是reveal,看似没有问题,但实际上如果在其他地方也会触发同样的逻辑时(比如按下键盘上某个键也会触发同样逻辑),就会造成多个事件处理执行了同样的逻辑,代码就需要被复制很多次;除此之外,测试时需要直接触发功能代码,而不需要通过模拟对元素的点击来触发。
因此将应用逻辑从所有事件处理中抽离是一种最佳实践,对上面的代码做重构:
1 | //good |
这样一来,处理应用逻辑的程序转移到了MyApplication.showPopup()
中,MyApplication.handleClick()
只做一件事,就是调用MyApplication.showPopup()
方法。这时对.showPopup()的调用就不需要依赖于click事件了。这一步骤就是优化事件处理的第一步,隔离应用逻辑。
做好上面的工作以后,还有一个问题就是event对象被无节制分发,从匿名事件处理函数传到了MyApplication.handleClick()
,又传到了MyApplication.showPopup()
中,由于event对象包含很多与事件相关的额外信息,而这里的代码只用到了其中的两个clientX
和clientY
,解决这个问题的最好办法就是让事件处理程序使用event对象,然后得到所有需要的数据后传给应用逻辑。
继续重构:
1 | //better |
这样一来,对于MyApplication.showPopup()
传入的参数就变得非常清晰,且可以随意测试或者在代码的任何位置调用这段逻辑。
当处理事件时,最好让事件处理程序成为唯一接触到event对象的函数,也就是说在事件处理程序中完成所有对event对象的必要操作,不要让应用逻辑对event产生依赖。