概述
監聽者模式其實就是一種簡單的前端思維模式,在前端領域遍地都是。 當它被應用到合適的場景下你就會感受到它的魅力。
下面我們拿商品曝光打點起個頭來講述一下監聽者模式可以怎麼來應用。
效能比較差的商品曝光方法
在網際網路電商領域,商品曝光打點可以說再平常不過了,我們需要通過商品的曝光度,來合理的安排商品主推等等。
那麼這東西可以怎麼實現呢?
一些同學可能會立馬說,在頁面滾動的時候去拿到每一個商品類目,再判斷是不是已經處在螢幕內,然後上報介面嘛!!!
當然這樣做肯定能達到商品曝光的目的,但是會不會存在什麼隱患或者效能問題呢?
我們可以看出如果每次滾動都去拿一遍商品類目,每次都需要遍歷訪問一次 dom
,然後每次都還需要拿到節點的 scrollTop
,這樣就又觸發了瀏覽器的重排重繪(重排重繪可是效能殺手哈?)。
換個角度想問題
我們現在來分析一下,這樣的方式問題出在哪幾個點:
- 每次滾動的時候你都需要遍歷子節點 ,
dom
操作的開銷可是非常大的,更何況那麼的頻繁,而且已經曝光的點也會被你遍歷取到,資料量一大的話~~~ - 判斷節點是否需要曝光的時候都需要實時取一遍位置資訊,這樣就又觸發了重排重繪,而且每次都需要取,好可怕~~
接下來我們就針對這幾個點來分析一下:
- 針對第一個點,我們可以變更一下思路,將節點資訊格式化初始化的時候找個陣列儲存起來。然後後續滾動觸發的時候就只是簡單的遍歷這個陣列,並且已經成功打點的項就從陣列中刪除,如此這個陣列的長度就是可控的
- 針對第二點,我們可以只在初始化的時候讀取一次
scrollTop
,同樣插入到被格式化的節點資料中。然後後續都從節點資料中取,這樣就大幅減少重排重繪
分析到這裡,好像已經比較之前的方式優美了不少,效能也優化了很多。但是我們還是需要每次都遍歷一個陣列去迴圈判斷是否曝光,而且陣列如果是動態(像下拉載入分頁這樣)的話,可能程式碼就會變的不怎麼優美。那麼有沒有什麼辦法可以讓程式碼更優美呢?接下來來介紹一下監聽者模式的應用~~
監聽者模式介紹
聽到監聽者模式,大家第一反應應該是事件系統,說的簡單一點就是訊息的訂閱和分發。mona-events這是一個簡單的JS
事件體系。
舉一個?:
假設有一天你的老大突然想出去吃頓好的,然後在微信群裡就吼了一聲說:“你們誰要跟我出去一起吃飯嗎?”
員工A:“我要回家吃飯,我不去。”
員工B:“我去的。”
員工C:“我要加班,我不去。”
...
這就是一個監聽者模式的引用了哈???
接下來我們再來舉一個例子對比一下: 同樣還是你的老大突然想出去吃飯,不過這次不是在微信群裡了,他是挨個挨個的問了一遍,然後問了30個人終於找到兩個人陪他出去吃飯了~~
讓我們為商品曝光打點提升一下幸福感
現在我們在每一個商品節點載入結束的時候就對這個商品繫結一個事件。 然後當頁面滾動的時候,只需要在全域性?一個通知,告知商品頁面滾動了,商品自己判斷自己需不需要進行曝光。 並且節點有自己的行為,跟容器節點相互獨立,因此就不需要考慮分頁這些會帶來的問題。
步驟:
- 商品節點自覺的進行滾動事件
命名:scroll-emit-event
監聽,並儲存節點scrollTop
值 - 容器滾動的時候分發
scroll-emit-event
訊息通知 - 商品節點自行判斷是否滿足曝光條件
感受到簡單的幸福之後,當然要更幸福啦
接下來我們通過幾個點來更好的優化一下:
- 頁面滾動當然要進行節流啦,你可以設定一個
200ms
的setTimeout
喲 - 商品上報曝光介面成功之後就要取消掉自個兒的監聽哈
介紹完這些之後大家可以聯想一下,圖片懶載入、頁面按需載入這些都可以按照這個套路來實現
這裡準備了兩個demo
這是我抽象出來的一個滾動監聽元件
這是基於 Mona系列 - React滾動監聽元件 實現的一個圖片懶載入元件
將監聽者模式跟業務相結合
經過上面的介紹之後,對事件應用有了一定的瞭解,那麼如何更具藝術性的在業務中使用,讓我們的程式碼更加清晰可維護呢?
這是一個在日常開發中非常常見的場景,通過彈層來進行一些操作,然後同步更新頁面資料
我們通常會通過將更新頁面資料的一個執行回撥傳遞到彈層模組中去,然後在彈層操作成功之後執行
這樣做自然沒有什麼問題,但是隨著業務的複雜度提升,我們可能需要傳遞的執行回撥就會越來越多,層級可能就不再是簡單的父子級關係 我們可以預見我們即將碰到跨很多級傳遞迴調函式的場景,程式碼即將變的難以維護
接下來我們來討論一個比較巧妙的方式:
// baseServer.js
import Events from 'mona-events';
export default class BaseServer extends Events {}
複製程式碼
// server.js
import BaseServer from 'core/baseServer'
class TestServer extends BaseServer {
test () {
return new Promise((resolve, reject) => {
// 模擬後端介面請求
setTimeout(() => {
resolve('你的模樣')
}, 1000)
}).then(res => {
this.emit('changeText', res)
return res
})
}
}
export default new TestServer
複製程式碼
// page.jsx
...
componentWillMount () {
TestServer.on('changeText', res => {
this.text = res
this.setState({})
})
}
...
複製程式碼
// modal.jsx
...
handleOk () {
this.loading = true
this.setState({})
TestServer.test().then(() => {
this.loading = false
Notification.info({
message: '文案修改成功!'
})
this.visible = false
this.setState({})
})
}
...
複製程式碼
通過程式碼我們可以看到,我們將監聽者模式跟後端請求介面巧妙的結合在了一起
當彈層上的操作請求成功返回之後我們分發一個changeText
事件,頁面收到這個訊息之後就觸發了更改頁面資訊的操作
✨✨這樣就巧妙的避免了多級傳遞執行回撥的問題,而且可以在任何地方觸發changeText
事件,規避掉了很多冗餘的重複邏輯,程式碼自然就變的簡單了
注意一點:這裡介紹了這種方法不代表所有這種場景都用這種方式,簡單的父子級關係當然是直接傳遞引數比較簡單易讀
之所以優美,是因為應用在了它最合適的場景