不多有時間對於寫過的代碼重構 此次發現重構是真的頗有意思的事情 因此就記錄下來了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