1. 前导
下面的内容,围绕下面这段代码展开,实现一个简单的计算器
import $ from 'jquery' // 模块化
const eventBus = $({}) // 事件总线
// mvc
const m = {
data: {
num: parseFloat(localStorage.getItem('app1.num') || 100)
},
update(data) {
Object.assign(m.data, data)
eventBus.trigger('m:updated')
localStorage.setItem('app1.num', m.data.num)
}
}
const v = {
el: null,
html: `
<div>
<div><span class="number">{num}</span></div>
<button class="add">+1</button>
<button class="min">-1</button>
<button class="mul">*2</button>
<button class="div">÷2</button>
</div>
`,
init(container) {
v.el = $(container)
},
render() {
if (v.el.children().length > 0) {
v.el.empty()
}
$(v.html.replace('{num}', m.data.num)).appendTo(v.el)
}
}
const c = {
init(container) {
v.init(container)
v.render(m.data.num)
c.toBindEvent()
eventBus.on('m:updated', () => {
v.render(m.data.num)
})
},
eventsList: { // 表驱动编程
'click .add': 'add',
'click .min': 'min',
'click .mul': 'mul',
'click .div': 'div'
},
add() {
m.update({ num: m.data.num + 1 })
},
min() {
m.update({ num: m.data.num - 1 })
},
mul() {
m.update({ num: m.data.num * 2 })
},
div() {
m.update({ num: m.data.num / 2 })
},
toBindEvent() {
for (let event in c.eventsList) {
let value = c.eventsList[event];
let spaceIndex = event.indexOf(' ')
let part1 = event.slice(0, spaceIndex)
let part2 = event.slice(spaceIndex)
$(v.el).on(part1, part2, c[value])
}
}
}
export default c
2. MVC
MVC 是一种设计模式,实现了视图与功能模块的分离;提高了程序的可维护性、可移植性、可扩展性与可重用性;降低了程序的开发难度
Model --- 数据模型,负责操作数据
View --- 和视图相关的,也就是UI界面展示相关的,描绘的是model的当前状态
Controller --- 剩下的其他逻辑
上述例子,最初的实现如下
import $ from 'jquery'
let num = parseFloat(localStorage.getItem('app1.num') || 100);
$('.number').text(num)
let add = document.selector('.add')
let min = document.selector('.min')
let mul = document.selector('.mul')
let div = document.selector('.div')
add.onclick = () => {
num += 1;
$('.number').text(num)
localStorage.setItem('app1.num', num)
}
min.onclick = () => {
num -= 1;
$('.number').text(num)
localStorage.setItem('app1.num', num)
}
mul.onclick = () => {
num *= 2;
$('.number').text(num)
localStorage.setItem('app1.num', num)
}
div.onclick = () => {
num /= 2;
$('.number').text(num)
localStorage.setItem('app1.num', num)
}
似乎这看起来更简单易懂一些,但是里面出现了很多重复的代码,采用MVC模式后,视图和数据分离,代码耦合性低,当要重新写一个功能模块时,可以以这个为模板去填充。
从上述例子也可以看出,MVC缺点也很明显,对于简单的小型项目而言,不一定需要采用该模式,因为你需要思考,如何划分这三块,以及实现模块间的通信
3. eventBus
eventBus 本质就是发布-订阅模式,达成任意组件间相互通信的作用。在一个地方触发(发布)事件,然后通过事件中心通知所有订阅者(订阅)
eventBus 提供了 on、off 和 trigger 等 API,on 用于监听事件,trigger 用于触发事件,off移除事件
3.1 eventBus的优缺点
**优点:**简化组件之间的通信方式,实现解耦让业务代码更加简洁
**缺点:**每个事件都必须自定义一个事件类,造成事件类太多,无形中加大了维护成本,大量使用 eventBus 会造成项目难以维护、问题难以定位
可以通过jQuery实现eventBus,因为jQuery自带on、off、trigger等API
const eventBus = $({})
vue实例中提供了emit等方法所以只需要创建一个空的vue实例,在组件中通过emit()去执行对应的事件并且可以传参来达到组件之间的通讯。
3.2 简单实现eventBus
class EventBus {
constructor() {
this.eventsList = {}
}
on(key, fn) {
if (!this.eventsList[key]) {
this.eventsList[key] = []
}
this.eventsList[key].push(fn)
}
emit(key, ...args) {
let arr = this.eventsList[key]
if (arr) {
arr.forEach(fn => {
fn(...args)
});
}
}
off(key, fn) {
let arr = this.eventsList[key]
if (arr) {
let index = arr.findIndex(f => f === fn)
if (index >= 0) {
arr.splice(index, 1)
}
}
}
}
PS:需要给事件分类别,要不然,emit触发事件时,会将事件列表的所有事件都会执行一遍
4. 表驱动编程
表驱动法就是一种编程模式,从表里面查找信息而不使用逻辑语句。事实上,凡是能通过逻辑语句来选择的事物,都可以通过查表来选择。对简单的情况而言,使用逻辑语句更为容易和直白。但随着逻辑链的越来越复杂,查表法也就愈发显得更具吸引力
表驱动只是为了优化复杂的逻辑判断,使其变得更灵活、易扩展。
比如,下面例子中页面需要根据后端返回的结果显示婚姻关系
let marryInfo = [
{ code: '10', value: '未婚' },
{ code: '20', value: '已婚' },
{ code: '30', value: '离异' },
{ code: '40', value: '再婚' },
{ code: '50', value: '丧偶' },
{ code: '60', value: '其他' }
]
let info = marryInfo.find((item) => {
return item.code === code
})
console.log(info)
document.querySelector('.showInfo').innerText = info.value || '其他'
上述就是简单的表驱动编程,将所有条件列出来,通过其中一个条件,找到对应的信息。相对于使用if...else或者switch,代码更简洁点,并且,修改条件时,只需要在marryInfo中修改对应的配置,只修改数据,不修改逻辑,灵活且风险低
5. es6 模块化
在创建JavaScript模块时,export 语句用于从模块中导出实时绑定的函数、对象或原始值,以便其他程序可以通过 import 语句使用它们。
静态的import 语句用于导入模块。无论是否声明了 strict mode ,导入的模块都运行在严格模式下。在浏览器中
- 命名导出(每个模块包含任意数量)
export {a,b,c} // 导出该文件中的a,b,c三个对象
import {a,b,c} from 'test.js' // 引入test.js的a,b,c三个对象
- 默认导出(没有大括号,因为每个模块只允许有一个默认导出)
export default aa //默认导出aa
import {default as aa} from 'test.js' // 完整写法
import aa from 'test.js' // 简写
- 重命名导入导出,在
import和export语句的大括号中,可以使用as关键字跟一个新的名字,来改变在顶级模块中将要使用的功能的标识名字
export {
userName as uName,
password as pwd
}
import { uName, pwd } from 'test.js'
或
export{ userName, password }
import {
userName as uName,
password as pwd } from 'test.js'
-
整个模块仅为副作用而导入,而不导入模块中的任何内容(接口)。
这将运行模块中的全局代码, 但实际上不导入任何值。
import './app1.js'
-
创建模块对象。
如果觉得一个个导入有点麻烦,可以试试将每一个模块功能导入到一个模块功能对象上
import * as Module from '/module.mjs'
这将获取 module.mjs 中所有可用的导出,并使它们可以作为对象模块的成员使用,从而有效地为其提供自己的命名空间
Module.function1()
- 动态加载模块
将 import() 作为函数调用,将其作为参数传递给模块的路径。它返回一个 promise,它用一个模块对象来实现,可以访问该对象的导出,例如
import('/module.js')
.then((module)=>{
module.function1()
...
})