原文:https://lwebapp.com/zh/post/event-bus
介紹
Event Bus
事件總線竣付,通常作為多個模塊間的通信機(jī)制诡延,相當(dāng)于一個事件管理中心,一個模塊發(fā)送消息古胆,其它模塊接受消息肆良,就達(dá)到了通信的作用。
比如逸绎,Vue 組件間的數(shù)據(jù)傳遞可以使用一個 Event Bus
來通信惹恃,也可以用作微內(nèi)核插件系統(tǒng)中的插件和核心通信。
原理
Event Bus
本質(zhì)上是采用了發(fā)布-訂閱的設(shè)計模式棺牧,比如多個模塊 A
巫糙、B
、C
訂閱了一個事件 EventX
颊乘,然后某一個模塊 X
在事件總線發(fā)布了這個事件参淹,那么事件總線會負(fù)責(zé)通知所有訂閱者 A
醉锄、B
、C
浙值,它們都能收到這個通知消息恳不,同時還可以傳遞參數(shù)。
// 關(guān)系圖
模塊X
?發(fā)布EventX
╔════════════════════════════════════════════════════════════════════╗
║ Event Bus ║
║ ║
║ 【EventX】 【EventY】 【EventZ】 ... ║
╚════════════════════════════════════════════════════════════════════╝
?訂閱EventX ?訂閱EventX ?訂閱EventX
模塊A 模塊B 模塊C
分析
如何使用 JavaScript 來實(shí)現(xiàn)一個簡單版本的 Event Bus
- 首先構(gòu)造一個
EventBus
類开呐,初始化一個空對象用于存放所有的事件 - 在接受訂閱時烟勋,將事件名稱作為 key 值,將需要在接受發(fā)布消息后執(zhí)行的回調(diào)函數(shù)作為 value 值筐付,由于一個事件可能有多個訂閱者神妹,所以這里的回調(diào)函數(shù)要存儲成列表
- 在發(fā)布事件消息時,從事件列表里取得指定的事件名稱對應(yīng)的所有回調(diào)函數(shù)家妆,依次觸發(fā)執(zhí)行即可
以下是代碼詳細(xì)實(shí)現(xiàn)鸵荠,可以復(fù)制到谷歌瀏覽器控制臺直接運(yùn)行檢測效果。
代碼
class EventBus {
constructor() {
// 初始化事件列表
this.eventObject = {};
}
// 發(fā)布事件
publish(eventName) {
// 取出當(dāng)前事件所有的回調(diào)函數(shù)
const callbackList = this.eventObject[eventName];
if (!callbackList) return console.warn(eventName + " not found!");
// 執(zhí)行每一個回調(diào)函數(shù)
for (let callback of callbackList) {
callback();
}
}
// 訂閱事件
subscribe(eventName, callback) {
// 初始化這個事件
if (!this.eventObject[eventName]) {
this.eventObject[eventName] = [];
}
// 存儲訂閱者的回調(diào)函數(shù)
this.eventObject[eventName].push(callback);
}
}
// 測試
const eventBus = new EventBus();
// 訂閱事件eventX
eventBus.subscribe("eventX", () => {
console.log("模塊A");
});
eventBus.subscribe("eventX", () => {
console.log("模塊B");
});
eventBus.subscribe("eventX", () => {
console.log("模塊C");
});
// 發(fā)布事件eventX
eventBus.publish("eventX");
// 輸出
> 模塊A
> 模塊B
> 模塊C
上面我們實(shí)現(xiàn)了最基礎(chǔ)的發(fā)布和訂閱功能伤极,實(shí)際應(yīng)用中蛹找,還可能有更進(jìn)階的需求。
進(jìn)階
1. 如何在發(fā)送消息時傳遞參數(shù)
發(fā)布者傳入一個參數(shù)到 EventBus
中哨坪,在 callback
回調(diào)函數(shù)執(zhí)行的時候接著傳出參數(shù)庸疾,這樣每一個訂閱者就可以收到參數(shù)了。
代碼
class EventBus {
constructor() {
// 初始化事件列表
this.eventObject = {};
}
// 發(fā)布事件
publish(eventName, ...args) {
// 取出當(dāng)前事件所有的回調(diào)函數(shù)
const callbackList = this.eventObject[eventName];
if (!callbackList) return console.warn(eventName + " not found!");
// 執(zhí)行每一個回調(diào)函數(shù)
for (let callback of callbackList) {
// 執(zhí)行時傳入?yún)?shù)
callback(...args);
}
}
// 訂閱事件
subscribe(eventName, callback) {
// 初始化這個事件
if (!this.eventObject[eventName]) {
this.eventObject[eventName] = [];
}
// 存儲訂閱者的回調(diào)函數(shù)
this.eventObject[eventName].push(callback);
}
}
// 測試
const eventBus = new EventBus();
// 訂閱事件eventX
eventBus.subscribe("eventX", (obj, num) => {
console.log("模塊A", obj, num);
});
eventBus.subscribe("eventX", (obj, num) => {
console.log("模塊B", obj, num);
});
eventBus.subscribe("eventX", (obj, num) => {
console.log("模塊C", obj, num);
});
// 發(fā)布事件eventX
eventBus.publish("eventX", { msg: "EventX published!" }, 1);
// 輸出
> 模塊A {msg: 'EventX published!'} 1
> 模塊B {msg: 'EventX published!'} 1
> 模塊C {msg: 'EventX published!'} 1
2. 訂閱后如何取消訂閱
有時候訂閱者只想在某一個時間段訂閱消息当编,這就涉及帶取消訂閱功能届慈。我們將對代碼進(jìn)行改造。
首先忿偷,要實(shí)現(xiàn)指定訂閱者取消訂閱金顿,每一次訂閱事件時,都生成唯一一個取消訂閱的函數(shù)鲤桥,用戶直接調(diào)用這個函數(shù)揍拆,我們就把當(dāng)前訂閱的回調(diào)函數(shù)刪除。
// 每一次訂閱事件茶凳,都生成唯一一個取消訂閱的函數(shù)
const unSubscribe = () => {
// 清除這個訂閱者的回調(diào)函數(shù)
delete this.eventObject[eventName][id];
};
其次嫂拴,訂閱的回調(diào)函數(shù)列表使換成對象結(jié)構(gòu)存儲,為每一個回調(diào)函數(shù)設(shè)定一個唯一 id
贮喧, 注銷回調(diào)函數(shù)的時候可以提高刪除的效率筒狠,如果還是使用數(shù)組的話需要使用 split
刪除,效率不如對象的 delete
箱沦。
代碼
class EventBus {
constructor() {
// 初始化事件列表
this.eventObject = {};
// 回調(diào)函數(shù)列表的id
this.callbackId = 0;
}
// 發(fā)布事件
publish(eventName, ...args) {
// 取出當(dāng)前事件所有的回調(diào)函數(shù)
const callbackObject = this.eventObject[eventName];
if (!callbackObject) return console.warn(eventName + " not found!");
// 執(zhí)行每一個回調(diào)函數(shù)
for (let id in callbackObject) {
// 執(zhí)行時傳入?yún)?shù)
callbackObject[id](...args);
}
}
// 訂閱事件
subscribe(eventName, callback) {
// 初始化這個事件
if (!this.eventObject[eventName]) {
// 使用對象存儲辩恼,注銷回調(diào)函數(shù)的時候提高刪除的效率
this.eventObject[eventName] = {};
}
const id = this.callbackId++;
// 存儲訂閱者的回調(diào)函數(shù)
// callbackId使用后需要自增,供下一個回調(diào)函數(shù)使用
this.eventObject[eventName][id] = callback;
// 每一次訂閱事件,都生成唯一一個取消訂閱的函數(shù)
const unSubscribe = () => {
// 清除這個訂閱者的回調(diào)函數(shù)
delete this.eventObject[eventName][id];
// 如果這個事件沒有訂閱者了运挫,也把整個事件對象清除
if (Object.keys(this.eventObject[eventName]).length === 0) {
delete this.eventObject[eventName];
}
};
return { unSubscribe };
}
}
// 測試
const eventBus = new EventBus();
// 訂閱事件eventX
eventBus.subscribe("eventX", (obj, num) => {
console.log("模塊A", obj, num);
});
eventBus.subscribe("eventX", (obj, num) => {
console.log("模塊B", obj, num);
});
const subscriberC = eventBus.subscribe("eventX", (obj, num) => {
console.log("模塊C", obj, num);
});
// 發(fā)布事件eventX
eventBus.publish("eventX", { msg: "EventX published!" }, 1);
// 模塊C取消訂閱
subscriberC.unSubscribe();
// 再次發(fā)布事件eventX,模塊C不會再收到消息了
eventBus.publish("eventX", { msg: "EventX published again!" }, 2);
// 輸出
> 模塊A {msg: 'EventX published!'} 1
> 模塊B {msg: 'EventX published!'} 1
> 模塊C {msg: 'EventX published!'} 1
> 模塊A {msg: 'EventX published again!'} 2
> 模塊B {msg: 'EventX published again!'} 2
3. 如何只訂閱一次
如果一個事件只發(fā)生一次套耕,通常也只需要訂閱一次谁帕,收到消息后就不用再接受消息。
首先冯袍,我們提供一個 subscribeOnce
的接口匈挖,內(nèi)部實(shí)現(xiàn)幾乎和 subscribe
一樣,只有一個地方有區(qū)別康愤,在 callbackId
前面的加一個字符 d
儡循,用來標(biāo)示這是一個需要刪除的訂閱。
// 標(biāo)示為只訂閱一次的回調(diào)函數(shù)
const id = "d" + this.callbackId++;
然后征冷,在執(zhí)行回調(diào)函數(shù)后判斷當(dāng)前回調(diào)函數(shù)的 id
有沒有標(biāo)示择膝,決定我們是否需要刪除這個回調(diào)函數(shù)。
// 只訂閱一次的回調(diào)函數(shù)需要刪除
if (id[0] === "d") {
delete callbackObject[id];
}
代碼
class EventBus {
constructor() {
// 初始化事件列表
this.eventObject = {};
// 回調(diào)函數(shù)列表的id
this.callbackId = 0;
}
// 發(fā)布事件
publish(eventName, ...args) {
// 取出當(dāng)前事件所有的回調(diào)函數(shù)
const callbackObject = this.eventObject[eventName];
if (!callbackObject) return console.warn(eventName + " not found!");
// 執(zhí)行每一個回調(diào)函數(shù)
for (let id in callbackObject) {
// 執(zhí)行時傳入?yún)?shù)
callbackObject[id](...args);
// 只訂閱一次的回調(diào)函數(shù)需要刪除
if (id[0] === "d") {
delete callbackObject[id];
}
}
}
// 訂閱事件
subscribe(eventName, callback) {
// 初始化這個事件
if (!this.eventObject[eventName]) {
// 使用對象存儲检激,注銷回調(diào)函數(shù)的時候提高刪除的效率
this.eventObject[eventName] = {};
}
const id = this.callbackId++;
// 存儲訂閱者的回調(diào)函數(shù)
// callbackId使用后需要自增肴捉,供下一個回調(diào)函數(shù)使用
this.eventObject[eventName][id] = callback;
// 每一次訂閱事件,都生成唯一一個取消訂閱的函數(shù)
const unSubscribe = () => {
// 清除這個訂閱者的回調(diào)函數(shù)
delete this.eventObject[eventName][id];
// 如果這個事件沒有訂閱者了叔收,也把整個事件對象清除
if (Object.keys(this.eventObject[eventName]).length === 0) {
delete this.eventObject[eventName];
}
};
return { unSubscribe };
}
// 只訂閱一次
subscribeOnce(eventName, callback) {
// 初始化這個事件
if (!this.eventObject[eventName]) {
// 使用對象存儲齿穗,注銷回調(diào)函數(shù)的時候提高刪除的效率
this.eventObject[eventName] = {};
}
// 標(biāo)示為只訂閱一次的回調(diào)函數(shù)
const id = "d" + this.callbackId++;
// 存儲訂閱者的回調(diào)函數(shù)
// callbackId使用后需要自增,供下一個回調(diào)函數(shù)使用
this.eventObject[eventName][id] = callback;
// 每一次訂閱事件饺律,都生成唯一一個取消訂閱的函數(shù)
const unSubscribe = () => {
// 清除這個訂閱者的回調(diào)函數(shù)
delete this.eventObject[eventName][id];
// 如果這個事件沒有訂閱者了窃页,也把整個事件對象清除
if (Object.keys(this.eventObject[eventName]).length === 0) {
delete this.eventObject[eventName];
}
};
return { unSubscribe };
}
}
// 測試
const eventBus = new EventBus();
// 訂閱事件eventX
eventBus.subscribe("eventX", (obj, num) => {
console.log("模塊A", obj, num);
});
eventBus.subscribeOnce("eventX", (obj, num) => {
console.log("模塊B", obj, num);
});
eventBus.subscribe("eventX", (obj, num) => {
console.log("模塊C", obj, num);
});
// 發(fā)布事件eventX
eventBus.publish("eventX", { msg: "EventX published!" }, 1);
// 再次發(fā)布事件eventX,模塊B只訂閱了一次复濒,不會再收到消息了
eventBus.publish("eventX", { msg: "EventX published again!" }, 2);
// 輸出
> 模塊A {msg: 'EventX published!'} 1
> 模塊C {msg: 'EventX published!'} 1
> 模塊B {msg: 'EventX published!'} 1
> 模塊A {msg: 'EventX published again!'} 2
> 模塊C {msg: 'EventX published again!'} 2
4. 如何清除某個事件或者所有事件
我們還希望通過一個 clear
的操作來將指定事件的所有訂閱清除掉脖卖,這個通常在一些組件或者模塊卸載的時候用到。
// 清除事件
clear(eventName) {
// 未提供事件名稱巧颈,默認(rèn)清除所有事件
if (!eventName) {
this.eventObject = {};
return;
}
// 清除指定事件
delete this.eventObject[eventName];
}
和取消訂閱的邏輯相似胚嘲,只不過這里統(tǒng)一處理了。
代碼
class EventBus {
constructor() {
// 初始化事件列表
this.eventObject = {};
// 回調(diào)函數(shù)列表的id
this.callbackId = 0;
}
// 發(fā)布事件
publish(eventName, ...args) {
// 取出當(dāng)前事件所有的回調(diào)函數(shù)
const callbackObject = this.eventObject[eventName];
if (!callbackObject) return console.warn(eventName + " not found!");
// 執(zhí)行每一個回調(diào)函數(shù)
for (let id in callbackObject) {
// 執(zhí)行時傳入?yún)?shù)
callbackObject[id](...args);
// 只訂閱一次的回調(diào)函數(shù)需要刪除
if (id[0] === "d") {
delete callbackObject[id];
}
}
}
// 訂閱事件
subscribe(eventName, callback) {
// 初始化這個事件
if (!this.eventObject[eventName]) {
// 使用對象存儲洛二,注銷回調(diào)函數(shù)的時候提高刪除的效率
this.eventObject[eventName] = {};
}
const id = this.callbackId++;
// 存儲訂閱者的回調(diào)函數(shù)
// callbackId使用后需要自增馋劈,供下一個回調(diào)函數(shù)使用
this.eventObject[eventName][id] = callback;
// 每一次訂閱事件,都生成唯一一個取消訂閱的函數(shù)
const unSubscribe = () => {
// 清除這個訂閱者的回調(diào)函數(shù)
delete this.eventObject[eventName][id];
// 如果這個事件沒有訂閱者了晾嘶,也把整個事件對象清除
if (Object.keys(this.eventObject[eventName]).length === 0) {
delete this.eventObject[eventName];
}
};
return { unSubscribe };
}
// 只訂閱一次
subscribeOnce(eventName, callback) {
// 初始化這個事件
if (!this.eventObject[eventName]) {
// 使用對象存儲妓雾,注銷回調(diào)函數(shù)的時候提高刪除的效率
this.eventObject[eventName] = {};
}
// 標(biāo)示為只訂閱一次的回調(diào)函數(shù)
const id = "d" + this.callbackId++;
// 存儲訂閱者的回調(diào)函數(shù)
// callbackId使用后需要自增,供下一個回調(diào)函數(shù)使用
this.eventObject[eventName][id] = callback;
// 每一次訂閱事件垒迂,都生成唯一一個取消訂閱的函數(shù)
const unSubscribe = () => {
// 清除這個訂閱者的回調(diào)函數(shù)
delete this.eventObject[eventName][id];
// 如果這個事件沒有訂閱者了械姻,也把整個事件對象清除
if (Object.keys(this.eventObject[eventName]).length === 0) {
delete this.eventObject[eventName];
}
};
return { unSubscribe };
}
// 清除事件
clear(eventName) {
// 未提供事件名稱,默認(rèn)清除所有事件
if (!eventName) {
this.eventObject = {};
return;
}
// 清除指定事件
delete this.eventObject[eventName];
}
}
// 測試
const eventBus = new EventBus();
// 訂閱事件eventX
eventBus.subscribe("eventX", (obj, num) => {
console.log("模塊A", obj, num);
});
eventBus.subscribe("eventX", (obj, num) => {
console.log("模塊B", obj, num);
});
eventBus.subscribe("eventX", (obj, num) => {
console.log("模塊C", obj, num);
});
// 發(fā)布事件eventX
eventBus.publish("eventX", { msg: "EventX published!" }, 1);
// 清除
eventBus.clear("eventX");
// 再次發(fā)布事件eventX机断,由于已經(jīng)清除楷拳,所有模塊都不會再收到消息了
eventBus.publish("eventX", { msg: "EventX published again!" }, 2);
// 輸出
> 模塊A {msg: 'EventX published!'} 1
> 模塊B {msg: 'EventX published!'} 1
> 模塊C {msg: 'EventX published!'} 1
> eventX not found!
5. TypeScript 版本
鑒于現(xiàn)在 TypeScript 已經(jīng)被大規(guī)模采用绣夺,尤其是大型前端項目,我們簡要的改造為一個 TypeScript 版本
可以復(fù)制以下代碼到 TypeScript Playground 體驗運(yùn)行效果
代碼
interface ICallbackList {
[id: string]: Function;
}
interface IEventObject {
[eventName: string]: ICallbackList;
}
interface ISubscribe {
unSubscribe: () => void;
}
interface IEventBus {
publish<T extends any[]>(eventName: string, ...args: T): void;
subscribe(eventName: string, callback: Function): ISubscribe;
subscribeOnce(eventName: string, callback: Function): ISubscribe;
clear(eventName: string): void;
}
class EventBus implements IEventBus {
private _eventObject: IEventObject;
private _callbackId: number;
constructor() {
// 初始化事件列表
this._eventObject = {};
// 回調(diào)函數(shù)列表的id
this._callbackId = 0;
}
// 發(fā)布事件
publish<T extends any[]>(eventName: string, ...args: T): void {
// 取出當(dāng)前事件所有的回調(diào)函數(shù)
const callbackObject = this._eventObject[eventName];
if (!callbackObject) return console.warn(eventName + " not found!");
// 執(zhí)行每一個回調(diào)函數(shù)
for (let id in callbackObject) {
// 執(zhí)行時傳入?yún)?shù)
callbackObject[id](...args);
// 只訂閱一次的回調(diào)函數(shù)需要刪除
if (id[0] === "d") {
delete callbackObject[id];
}
}
}
// 訂閱事件
subscribe(eventName: string, callback: Function): ISubscribe {
// 初始化這個事件
if (!this._eventObject[eventName]) {
// 使用對象存儲欢揖,注銷回調(diào)函數(shù)的時候提高刪除的效率
this._eventObject[eventName] = {};
}
const id = this._callbackId++;
// 存儲訂閱者的回調(diào)函數(shù)
// callbackId使用后需要自增陶耍,供下一個回調(diào)函數(shù)使用
this._eventObject[eventName][id] = callback;
// 每一次訂閱事件,都生成唯一一個取消訂閱的函數(shù)
const unSubscribe = () => {
// 清除這個訂閱者的回調(diào)函數(shù)
delete this._eventObject[eventName][id];
// 如果這個事件沒有訂閱者了她混,也把整個事件對象清除
if (Object.keys(this._eventObject[eventName]).length === 0) {
delete this._eventObject[eventName];
}
};
return { unSubscribe };
}
// 只訂閱一次
subscribeOnce(eventName: string, callback: Function): ISubscribe {
// 初始化這個事件
if (!this._eventObject[eventName]) {
// 使用對象存儲烈钞,注銷回調(diào)函數(shù)的時候提高刪除的效率
this._eventObject[eventName] = {};
}
// 標(biāo)示為只訂閱一次的回調(diào)函數(shù)
const id = "d" + this._callbackId++;
// 存儲訂閱者的回調(diào)函數(shù)
// callbackId使用后需要自增,供下一個回調(diào)函數(shù)使用
this._eventObject[eventName][id] = callback;
// 每一次訂閱事件坤按,都生成唯一一個取消訂閱的函數(shù)
const unSubscribe = () => {
// 清除這個訂閱者的回調(diào)函數(shù)
delete this._eventObject[eventName][id];
// 如果這個事件沒有訂閱者了毯欣,也把整個事件對象清除
if (Object.keys(this._eventObject[eventName]).length === 0) {
delete this._eventObject[eventName];
}
};
return { unSubscribe };
}
// 清除事件
clear(eventName: string): void {
// 未提供事件名稱,默認(rèn)清除所有事件
if (!eventName) {
this._eventObject = {};
return;
}
// 清除指定事件
delete this._eventObject[eventName];
}
}
// 測試
interface IObj {
msg: string;
}
type PublishType = [IObj, number];
const eventBus = new EventBus();
// 訂閱事件eventX
eventBus.subscribe("eventX", (obj: IObj, num: number, s: string) => {
console.log("模塊A", obj, num);
});
eventBus.subscribe("eventX", (obj: IObj, num: number) => {
console.log("模塊B", obj, num);
});
eventBus.subscribe("eventX", (obj: IObj, num: number) => {
console.log("模塊C", obj, num);
});
// 發(fā)布事件eventX
eventBus.publish("eventX", { msg: "EventX published!" }, 1);
// 清除
eventBus.clear("eventX");
// 再次發(fā)布事件eventX臭脓,由于已經(jīng)清除酗钞,所有模塊都不會再收到消息了
eventBus.publish<PublishType>("eventX", { msg: "EventX published again!" }, 2);
// 輸出
[LOG]: "模塊A", {
"msg": "EventX published!"
}, 1
[LOG]: "模塊B", {
"msg": "EventX published!"
}, 1
[LOG]: "模塊C", {
"msg": "EventX published!"
}, 1
[WRN]: "eventX not found!"
6. 單例模式
在實(shí)際使用過程中,往往只需要一個事件總線就能滿足需求来累,這里有兩種情況算吩,保持在上層實(shí)例中單例和全局單例。
- 保持在上層實(shí)例中單例
將事件總線引入到上層實(shí)例使用佃扼,只需要保證在一個上層實(shí)例中只有一個 EventBus
偎巢,如果上層實(shí)例有多個,意味著有多個事件總線兼耀,但是每個上層實(shí)例管控自己的事件總線压昼。
首先在上層實(shí)例中建立一個變量用來存儲事件總線,只在第一次使用時初始化瘤运,后續(xù)其他模塊使用事件總線時直接取得這個事件總線實(shí)例窍霞。
代碼
// 上層實(shí)例
class LWebApp {
private _eventBus?: EventBus;
constructor() {}
public getEventBus() {
// 第一次初始化
if (this._eventBus == undefined) {
this._eventBus = new EventBus();
}
// 后續(xù)每次直接取唯一一個實(shí)例,保持在LWebApp實(shí)例中單例
return this._eventBus;
}
}
// 使用
const eventBus = new LWebApp().getEventBus();
- 全局單例
有時候我們希望不管哪一個模塊想使用我們的事件總線拯坟,我們都想這些模塊使用的是同一個實(shí)例但金,這就是全局單例,這種設(shè)計能更容易統(tǒng)一管理事件郁季。
寫法同上面的類似冷溃,區(qū)別是要把 _eventBus
和 getEventBus
轉(zhuǎn)為靜態(tài)屬性。使用時無需實(shí)例化 EventBusTool
工具類梦裂,直接使用靜態(tài)方法就行了似枕。
代碼
// 上層實(shí)例
class EventBusTool {
private static _eventBus?: EventBus;
constructor() {}
public static getEventBus(): EventBus {
// 第一次初始化
if (this._eventBus == undefined) {
this._eventBus = new EventBus();
}
// 后續(xù)每次直接取唯一一個實(shí)例,保持全局單例
return this._eventBus;
}
}
// 使用
const eventBus = EventBusTool.getEventBus();
原文:https://lwebapp.com/zh/post/event-bus
總結(jié)
以上是小編對 Event Bus
的一些理解年柠,基本上實(shí)現(xiàn)了想要的效果凿歼。通過自己動手實(shí)現(xiàn)一遍發(fā)布訂閱模式,也加深了對經(jīng)典設(shè)計模式的理解。其中還有很多不足和需要優(yōu)化的地方答憔,歡迎大家多多分享自己的經(jīng)驗味赃。