一系列算法封装起来,并使它们相互之间可以替换。被封装起来的算法具有独立性,外部不可改变其特性
数据字典
个人理解中数据字典天然符合策略模式的概念 所以将其归类于策略模式的应用
const dates = ['日','一','二','三','四','五','六']
console.log(`今天是星期${dates[new Date().getDay()]}`)
const statusArr = ['保存','编辑','审核','审核通过','审核不通过','关闭']
statusArr[0] //=>保存
statusArr[3] //=>审核通过
statusArr[6] //=>关闭
// 以上是一个简单的枚举状态回显例子
const statusObj = {
'new':'新增',
'edit':'编辑',
'pass':'审核',
'close':'关闭'
}
let code = 'close'
statusObj[code]
// 非枚举状态下的回显 实际业务可能存在非枚举的的类型 依旧可以通过这种方式回显
//以下是模拟i18n场景的数据字典
const DEFAULT = 'zh-CN' // 默认读取
const dictionary = { // 国际化字典表
'zh-CN':{
hello:'你好',
message:'消息',
home:'首页'
},
'en':{
hello:'hello',
message:'message',
home:'home'
}
}
// 安装新的数据字典方法
function installNew(params,info){
Object.entries(info).forEach(([key,text])=>{
// 转换传入的对象为键值对数组
// params 为需要按照的字段
// 这里的key 对应语言
// 这里的text 对应实际文本
dictionary[key][params] = text
})
}
// 翻译方法
function i18n(language,params){
const info = dictionary[language] || dictionary[DEFAULT]
// 根据传入的语言提取字典的对应语言对象 如果没有读取到那么读取默认语言对象
return info[params] // 返回语言对象的对应参数
}
installNew('user',{
// 需要安装user这个字段
'zh-CN':'用户',
en:'user'
})
i18n('zh-CN','user') // => 用户
i18n('en','user') // => user
i18n('zh-CN','home') // => 首页
i18n('en','home') // => home
i18n('jp','home') // => 首页
自动分发
🌰 汽车总站会有不同线路的公交 他们之间线路形式可能部分重叠,目的站不一定相同
那么我们从选择一条路线的公交上车必定是按照既有的路线行驶
策略模式的行为与齐差不多
// 这里我们假设一个业务场景并且用策略模式实现
// 1. 按下 a键 输出描述 '你好'
// 2. 按下 b键 弹出一个时间戳
// 3. 按下s 执行 a键逻辑 然后执行b键逻辑 但是不增加策略执行次数
// 4. 按下esc 输出执行不同策略的次数 要求自动添加在新增新增策略是不需要添加额外代码处理次数记录
const bus = {
run(e){ // 自动分发路口 当成例子中的汽车总站
const {key} = e // 从传入的键盘事件提取按键的key
const fn = this.methods[key] // 从自身的方法
if(fn){
this.nums[key] ? this.nums[key]++ : this.nums[key] = 1 // 判断是否执行过这个策略如果没有那么直接设置为1 如果有就增长1
fn.call(this,e) // 这里修改方法的this指向使其可以读取整个策略对象
}
},
nums:{},// => 储存执行次数数据
methods:{
a(){
console.log('你好')
},
b(){
alert(+new Date())
},
s(){
this.methods.a()
this.methods.b()
},
Escape(){
console.table(this.nums)
}
}
}
window.addEventListener('keydown',e=>bus.run(e))
使用策略模式开发一个表格类的数据单向更新 以及方向键移动
- 支持快捷键上下左右移动焦点
- 表格数据可以新增/删除/编辑并且实时同步到数据源
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title></title>
<style>
table {
border-collapse: collapse;
}
th,
td {
border: 1px solid #9c9c9c;
width: 200px;
}
input[type="text"] {
padding: 0px;
margin: 0px;
outline: 1px;
width: 100%;
border: 0px;
height: 30px;
}
input[type="text"]:focus {
box-shadow: 0px 0px 1px 2px #9f9f9f;
}
</style>
</head>
<body>
<input value="新增一行" type="button" e-click="addRow" />
<table>
<tbody></tbody>
</table>
</body>
<script>
function on(type, lister) { //全局监听封装简化代码量
return window.addEventListener(type, lister)
}
function require(msg) {// 必传参数校验
throw new Error(msg)
}
class MyTable {
_config = { // 默认配置
el: 'table',
cols: [],
data: []
}
dom = null // 操作的实例
constructor(config) {
Object.assign(this._config, config)//拷贝传入的配置到自身
const { el } = this._config // 提取参数
this.dom = document.querySelector(el) || require('不存在的dom') // 获取dom没有获取到自动报错结束实例化
this.renderHeader() //渲染表头
this.load() // 渲染内容
this.addLiser() // 监听表格
}
addLiser() {
on('click', e => {
// 元素被点击 => 提取来源dom => 尝试提取dom上配置的e-click属性 => 如果存在主动调用累自身方法,这里只做demo就不写兼容代码了
// 监听点击事件 来源dom如果存在 e-click 属性 自动读取自身对应的方法名去执行
const { target } = e
const eName = target.attributes['e-click']
if (eName) {
this[eName.value](e)
}
})
on('input', e => {
// 元素出发input事件 => 尝试提取e-input的值 => 如果实例自身存在对应方法 => 主动调用方法传入事件 => 提取事件来源input框自定义的index field => 根据index提取当前行对应的数据对象通过field动态设置对应属性的值
// 监听点击事件 来源dom如果存在 e-input 属性 自动读取自身对应的方法名去执行
const { target } = e
const eName = target.attributes['e-input']
if (eName) {
this[eName.value](e)
}
})
on('keydown', e => {
// 监听方向键 自动分发到实例内部对应的切换焦点方法
const { key, target } = e
if (target.attributes.focus && typeof this[key] === 'function') {
const { index, field } = target.dataset
this[key](target, index * 1, field)
}
})
}
getNext(index, field) {
// 获取传入的 索引行 以及 字段的焦点内容
return this.dom.querySelector(`[data-index="${index}"][data-field="${field}"][focus]`)
}
getFocusList(index) {
// 获取当前行所有可聚焦的dom
return this.dom.querySelectorAll(`[data-index="${index}"][focus]`)
}
ArrowUp(target, index, field) {
//获取上一行同字段的输入框 没有就使用自身
const el = this.getNext(index - 1, field) || target
el.focus()
el.select()
}
ArrowDown(target, index, field) {
const el = this.getNext(index + 1, field) || target
el.focus()
el.select()
}
ArrowLeft(target, index, field) {
// 获取当前行所有可聚焦的输入框然后根据自身索引找到上一个
const els = [...this.dom.querySelectorAll(`[data-index="${index}"][focus]`)]
const i = els.indexOf(target)
const prv = els[i - 1] || target
prv.focus()
prv.select()
}
ArrowRight(target, index, field) {
const els = [...this.dom.querySelectorAll(`[data-index="${index}"][focus]`)]
const i = els.indexOf(target)
const next = els[i + 1] || target
next.focus()
next.select()
}
renderHeader() {
const vm = document.createElement('table')
const ths = this._config.cols.reduce((prv, next) => `${prv}<th>${next.title || ''}</th>`, '')
vm.innerHTML = `<thead><tr>${ths}<th>操作</th></tr></thead>`
this.dom.append(...vm.children)
}
load() {
const { cols, data } = this._config
const body = this.dom.querySelector('tbody')
// 渲染所有行内容的字符串
const trs = data.reduce((prv, next, index) => {
return prv + MyTable.renderTr(cols, next, index)
}, '')
body.innerHTML = trs // 替换表格体内容
console.table(this._config.data)
}
static renderTr(cols, row, index) {
// 批量渲染行数据
return `<tr>
${cols.reduce((prv, next) => {
return prv + MyTable.renderTD(row, index, next.field)
}, '')}
<td>
<input value="删除" type="button" e-click="removeRow" data-index="${index}"/>
</td>
</tr>`
}
static renderTD(row, index, field) {
// 渲染单个数据内容是 td标签内包含input
// focus标记 => 可聚焦内容
// e-input => 输入内容后触发input事件
// data-index => 数据对象索引
// data-field => 数据字段
return `<td><input focus type="text" e-input="update" data-index="${index}" data-field="${field}" value="${row[field] || ''}" /></td>`
}
addRow() {
// 推入空数据
this._config.data.push({})
// 重新画表格
this.load()
}
removeRow(e) {
const { target } = e
const { index } = target.dataset
// 提取索引
this._config.data.splice(index, 1)
// 删除数组中的对应索引内容
this.load()
// 重新画表格
}
update(e) {
const { target } = e
const { index, field, value } = target.dataset
// 提取索引 和 数据字段
// 根据索引提取对象 根据字段更新值
this._config.data[index][field] = target.value
console.table(this._config.data)
}
}
new MyTable({
cols: [
{
field: 'msg',
title: '消息'
},
{
field: 'user',
title: '用户',
},
{
field: 'remark',
title: '备注'
}
],
data: [{}]
})
</script>
</html>