不多有时间对于写过的代码重构 此次发现重构是真的颇有意思的事情 因此就记录下来了javascript
集卡属于互动类型的游戏,此页面有9个弹窗,其中有同时出现的5个弹窗的状况,且若是同时出现必须按照指定顺序弹出。
遇到复杂的交互逻辑,数据结构能够帮助理清思路,抽象逻辑,完成稳定可靠的代码。在此次交互中,弹框要一个个按照顺序弹出,能够虑有序队列。可是弹框的弹出和关闭属于事件。在上一个弹框弹出关闭后,触发下一个弹框弹出。能够考虑事件的发布订阅。css
class Queue {
constructor() {
this.dataStore = []
}
enqueue(e) {
this.dataStore.push(e)
}
dequeue() {
this.dataStore.shift()
}
front() {
return this.dataStore[0]
}
back() {
return this.dataStore[this.dataStore.length - 1]
}
isEmpty() {
if (this.dataStore.length === 0) {
return true
}
return false
}
toString() {
return this.dataStore.join(",")
}
}
复制代码
jest 写些测试用例验证前端
import Queue from './utils'
describe('Queue', () => {
const q = new Queue()
q.enqueue('string1')
q.enqueue('test')
q.enqueue(3)
test('queue', () => {
expect(q.toString()).toBe('string1,test,3')
q.dequeue()
expect(q.front()).toBe('test')
expect(q.back()).toBe('3')
expect(q.toString()).toBe('test,3')
expect(q.isEmpty()).toBe(false)
q.dequeue()
q.dequeue()
expect(q.isEmpty()).toBe(true)
})
})
复制代码
队列的弹窗展现有三个状态java
弹窗展现时加入队列的状态以下node
总体逻辑分析以下图ajax
import { EventEmitter } from "events"
const emitter = new EventEmitter()
const queue = new Queue()
// 事件中心
export class EventCenter {
// 添加绑定事件
static on(type, cb) {
this[`${type}`] = cb
emitter.on(type, this[`${type}`])
}
// 执行绑定事件回掉
static emit(type, data) {
emitter.emit(type, data)
// 每次回掉后 就接触绑定
if (type.indexOf("Close") > -1) {
this.remove(type)
}
}
// 解除绑定事件
static remove(type) {
emitter.removeListener(type, this[`${type}`])
}
static eventNames() {
return emitter.eventNames()
}
}
// 一个弹窗的队列
class Queue {
constructor() {
this.dataStore = []
}
enqueue(e) {
this.dataStore.push(e)
}
dequeue() {
this.dataStore.shift()
}
front() {
return this.dataStore[0]
}
back() {
return this.dataStore[this.dataStore.length - 1]
}
isEmpty() {
if (this.dataStore.length === 0) {
return true
}
return false
}
toString() {
return this.dataStore.join(",")
}
}
/**
* 将弹窗事件名推入队列
*/
export const push = eventName => {
if (queue.isEmpty()) {
queue.enqueue(eventName)
openDialog() // 启动出队逻辑
} else {
queue.enqueue(eventName) // 循环中依然能够同时入队新的元素
}
}
/**
* 打开弹窗,递归,循环出队
*/
const openDialog = () => {
// 打开弹窗
EventCenter.emit(queue.front())
// 监听弹窗关闭
EventCenter.on(`${queue.front()}Close`, () => {
queue.dequeue() // 出队
if (!queue.isEmpty()) {
// 队列不为空时,递归
openDialog()
}
})
}
复制代码
import { push } from "./utils"
push(MODAL_TYPE.GIVE_CARD)
push(MODAL_TYPE.ASSIST_CARD)
push(MODAL_TYPE.BUY_CARD)
push(MODAL_TYPE.TASK_CARD)
setTimeOut(()=>{
push(MODAL_TYPE.RECEIVE_CARD)
},1000)
setTimeOut(()=>{
push(MODAL_TYPE.ASSIST_CARD)
},4000)
复制代码
toast.show('我要第一个出现')
toast.show('我要第二个出现')
toast.show('我要第三个出现')
复制代码
游戏状态多意味着常量多,好的代码最好是不写注释也一目了然。若是能够把注释经过代码表示出来那就太棒了,限制维护者强制书写注释那就更好了。后端
// 建立enum 数据
/** *
* 数据类型
* KEY:{
* value:string,
* desc:string
* }
*
* enum[KEY] => value:string
* enum.keys() => KEYS: Array<KEY>
* enum.values() => values: Array<value>
*
*/
export const createEnum = enumObj => {
const keys = target => Object.keys(target) || []
const values = target =>
(Object.keys(target) || []).map(key => {
if (
typeof target[key] === "object" &&
target[key].hasOwnProperty("value")
) {
return target[key].value
} else if (
typeof target[key] === "object" &&
!target[key].hasOwnProperty("value")
) {
return key
}
})
const handler = {
get: function(target, key) {
switch (key) {
// keys 获取key => Array<key:string>
case "keys":
return () => keys(target)
// values Array<key:string> || Array<Object<key:string,value:string>>
case "values":
return () => values(target)
// 获取 详细描述 descs TODO
// [key,[value]] 键值对 entries TODO
// 合并 assign TODO
default:
break
}
if (target[key]) {
if (
typeof target[key] === "object" &&
target[key].hasOwnProperty("value")
) {
return target[key].value
} else if (
typeof target[key] === "object" &&
!target[key].hasOwnProperty("value")
) {
return key
}
return target[key]
}
throw new Error(`No such enumerator: ${key}`)
},
}
return new Proxy(enumObj, handler)
}
复制代码
export const MODAL_TYPE = createEnum({
GIVE_CARD: {
value: "giveCard",
desc: "天降弹窗",
},
ASSIST_CARD: {
value: "assistCard",
desc: "助力弹窗",
},
BUY_CARD: {
value: "buyCard",
desc: "下单弹窗",
},
TASK_CARD: {
value: "taskCard",
desc: "任务弹窗",
},
RECEIVE_CARD: {
value: "receiveCard",
desc: "收卡弹窗",
},
SEND_CARD: {
value: "sendCard",
desc: "送卡弹窗",
},
})
复制代码
思路:用户点击哪一个nav,记录当前scrollTop。等待下次切换到对应逻辑时候就设置对应scrollTop 这里面的变量只有 scrollTop 和nav的index,且与ui和数据无关。有本身单独的数据存储,对外界无依赖。因此能够抽离出来。数组
// 判断点击是否须要吸顶 不设置为单例 因此记得unmount的时候销毁
// pravite
// 1. 缓存旧的 scrolltop ✅
// 2. 设置当前点击 active 的 scrolltop ✅
// 3. 判断是否吸顶
// 4. 设置列表的height min-height 为 100vh
// public
// 1. getScrollTop
// 2. onChange
// 3. setMinScrollTop
export class CacheListScrollTop {
constructor(...rest) {
this._init(...rest)
}
_init(minScrollTop, activeIndex, node) {
this.minScrollTop = minScrollTop
this.shouldSetMinScrollTop = false
this.activeIndex = activeIndex || 0 // 设置起始值
this.node = node
this.scrollTop = new Map()
this.DIST = 1
}
// 保存scrolltop
_cachePreviousScrollTop() {
const prevoisActiveIndex = this.activeIndex
const body = document.documentElement.scrollTop
? document.documentElement
: document.body
const node = this.node || body
const prevoisTop = Math.abs(node.getBoundingClientRect().top)
const scrollTop = new Map(this.scrollTop)
scrollTop.set(prevoisActiveIndex, prevoisTop)
this.scrollTop = scrollTop
}
_setNextScrollTop(index) {
this._cachePreviousScrollTop()
this.prevoisIndex = this.activeIndex
this.activeIndex = Number(index)
const activeNavScrollTop = this.scrollTop.get(Number(index)) || 0
// 设置最小值 scrollTop <= 最小高度 => 设置最小吸顶量
if (
activeNavScrollTop <= this.minScrollTop &&
this.shouldSetMinScrollTop
) {
this._setScrollTop(this.minScrollTop + this.DIST)
return false
}
// 设置最小值 scrollTop > 最小高度 => 设置 scrollTop
if (
activeNavScrollTop > this.minScrollTop &&
this.shouldSetMinScrollTop
) {
this._setScrollTop(activeNavScrollTop)
return false
}
// 不设置最小值 统一设置 scrollTop
const body = document.documentElement.scrollTop
? document.documentElement
: document.body
const node = this.node || body
const prevoisTop = Math.abs(node.getBoundingClientRect().top)
const keys = this.scrollTop.keys()
const scrollTop = new Map()
;([ ...keys ] || []).forEach((key) => {
scrollTop.set(key, prevoisTop)
})
this.scrollTop = scrollTop
this._setScrollTop(prevoisTop)
return false
}
_setScrollTop(scrollTop) {
if (this.node) {
this.node.scrollTop = scrollTop
return
}
document.documentElement.scrollTop = scrollTop
document.body.scrollTop = scrollTop
}
// 获取scrollTop 集合
get getScrollTop() {
return this.scrollTop
}
// scrollTop是否设置 最小值
setMinScrollTop(shouldSetMinScrollTop ) {
this.shouldSetMinScrollTop = shouldSetMinScrollTop
}
onChange(index) {
this._setNextScrollTop(index)
}
}
复制代码
此结果对比都是本地跑服务后 使用election截屏工具后保存缓存
优化前 白屏数量 | 优化后 白屏图片数量 | 优化前 资源加载完成 | 优化后 资源加载完成 |
---|---|---|---|
8 | 6 | 10 | 8 |
7 | 6 | 9 | 8 |
5 | 5 | 9 | 7 |
11 | 6 | 13 | 8 |
10 | 7 | 12 | 9 |
6 | 7 | 8 | 9 |
7 | 6 | 9 | 8 |
9 | 9 | 11 | 11 |
7 | 9 | 9 | 11 |
8 | 6 | 10 | 8 |
优化前 白屏平均数 | 优化后 白屏平均数 | 优化前 资源加载平均数 | 优化后 资源加载平均数 |
---|---|---|---|
7.5 | 6.7 | 10 | 7.8 |
很明显看到 资源加载速度改变 收屏加载速度也有减小bash