觀察者模式:觀察者模式(Observer mode)指的是函數(shù)自動(dòng)觀察數(shù)據(jù)對(duì)象,一旦對(duì)象有變化,函數(shù)就會(huì)自動(dòng)執(zhí)行。而js中最常見的觀察者模式就是事件觸發(fā)機(jī)制。
現(xiàn)在,讓我們重新實(shí)現(xiàn)一個(gè)自定義事件。
ES5實(shí)現(xiàn)
要實(shí)現(xiàn)一個(gè)自定義事件,首先要想清楚我們要干什么。
1.要有一個(gè)對(duì)象,存儲(chǔ)著它自己的觸發(fā)函數(shù),所以他的觸發(fā)函數(shù)應(yīng)該是這種類型
handler={
type1:
type2:
...
}
2.這個(gè)對(duì)象的觸發(fā)函數(shù)可能有很多種,比如一個(gè)onclick可能觸發(fā)多個(gè)事件,那么handler的屬性應(yīng)該是一個(gè)數(shù)組,每個(gè)數(shù)組的值都是一個(gè)函數(shù)
handler={
type1:[func1,func2...],
type2:[func2,func4...],
...
}
3.現(xiàn)在這個(gè)對(duì)象的主體部分已經(jīng)思考好了,現(xiàn)在就是要它‘動(dòng)起來’,給它添加各種動(dòng)作。
一個(gè)事件可能有哪些動(dòng)作呢?
- add:添加事件某種類型的函數(shù),
- remove: 移除某種類型的函數(shù),
- fire:觸發(fā)某種類型的函數(shù),
- once:觸發(fā)某種類型的函數(shù),然后移除掉這個(gè)函數(shù),
那么現(xiàn)在我們的自定義事件的骨架已經(jīng)搭建好了。
eventOb={
//函數(shù)儲(chǔ)存
handler:{
type1:[func1,func2...],
type2:[func2,func4...],
...
},
//主要事件
add:function(){},
remove:function(){},
fire:function(){},
once:function(){},
}
下面開始填充這些函數(shù),
1.add
添加一個(gè)事件監(jiān)聽,首先傳入?yún)?shù)應(yīng)該是 事件類型type,和觸發(fā)函數(shù) func,傳入的時(shí)候檢測有沒有這個(gè)函數(shù),有了就不重復(fù)添加,那么代碼并不難,應(yīng)該是下面這樣。
add:function (type,func) {
//檢測type是否存在
if(eventOb.handleFunc[type]){
//檢測事件是否存在,不存在則添加
if(eventOb.handleFunc[type].indexOf(func)===-1){
eventOb.handleFunc[type].push(func);
}
}else{
eventOb.handleFunc[type]=[func];
}
},
2.remove
remove也需要指明傳入類型和函數(shù),和add很相像,首先我們就寫成下面的樣子
remove:function (type,func) {
let target = eventOb.handleFunc[type];
let index=target.indexOf(func);
target.splice(index,1);
},
看起來很簡單,但是remove有一個(gè)潛在的需求,就是如果你的事件不存在,它應(yīng)該會(huì)報(bào)錯(cuò)。而這里不會(huì)報(bào)錯(cuò),index在func不存在的時(shí)候是-1;這時(shí)候不能正確的刪除,也不會(huì)報(bào)錯(cuò)。
所以我們需要改進(jìn)一下。
remove:function (type,func) {
try{
let target = eventOb.handleFunc[type];
let index=target.indexOf(func);
if(index===-1)throw error;
target.splice(index,1);
}catch (e){
console.error('別老想搞什么飛機(jī),刪除我有的東西!');
}
},
當(dāng)index=-1拋出一個(gè)錯(cuò)誤,好了,解決的很簡陋,但是效果還行。
3.fire
觸發(fā)一個(gè)點(diǎn)擊事件肯定是要觸發(fā)它全部的函數(shù),這里也是一樣,所以只需要傳入type,然后事件可能不存在,像上面一樣處理。
fire:function (type,func) {
try{
let target = eventOb.handleFunc[type];
let count = target.length;
for (var i = 0; i < count; i++) {
//加()使立即執(zhí)行
target[i]();
}
}catch (e){
console.error('別老想搞什么飛機(jī),觸發(fā)我有的東西!');
}
},
看起來很還好。但是。。。嗯。先說once。
4.once
once這里應(yīng)該相當(dāng)簡單。感覺起來是這樣。。。fire,然后remove?來試試
once:function (type,func) {
eventOb.fire(type);//不能觸發(fā)某個(gè)事件,應(yīng)該為fire(type,func)
eventOb.remove(type, func);
}
這就發(fā)現(xiàn)了問題,我只想觸發(fā)并且刪除某個(gè)事件怎么辦,fire一下就全觸發(fā)了呀。
所以fire的問題就顯現(xiàn)出來了。我們還是要給它一個(gè)func,但是可選。讓我們改寫一下fire
fire:function (type,func) {
try{
let target = eventOb.handleFunc[type];
if(arguments.length===1) {
//不傳func則全部觸發(fā)
let count = target.length;
for (var i = 0; i < count; i++) {
target[i]();
}
}else{
//傳func則觸發(fā)func
let index=target.indexOf(func);
if(index===-1)throw error;
func();
}
//need some code
}catch (e){
console.error('別老想搞什么飛機(jī),觸發(fā)我有的東西!');
//need some code
}
},
這樣once就寫好了?
試試,對(duì)一個(gè)不存在的事件觸發(fā)once,發(fā)現(xiàn)once會(huì)報(bào)兩個(gè)錯(cuò)誤,

這樣當(dāng)然不太好,想想,嗯可以給fire一個(gè)返回值,當(dāng)fire報(bào)錯(cuò)就return false不執(zhí)行remove
最后的once長這樣
once:function (type,func) {
eventOb.fire(type, func)?
eventOb.remove(type, func):null;
}
并且在fire上面 need code的地方補(bǔ)上return true 和return false,大功告成~
完整代碼:
var eventOb={
handleFunc:{},
add:function (type,func) {
if(eventOb.handleFunc[type]){
//檢測事件是否存在,不存在則添加
if(eventOb.handleFunc[type].indexOf(func)===-1){
eventOb.handleFunc[type].push(func);
}
}else{
eventOb.handleFunc[type]=[func];
}
},
fire:function (type,func) {
try{
let target = eventOb.handleFunc[type];
if(arguments.length===1) {
let count = target.length;
for (var i = 0; i < count; i++) {
target[i]();
}
}else{
let index=target.indexOf(func);
if(index===-1)throw error;
func();
}
return true;
}catch (e){
console.error('別老想搞什么飛機(jī),觸發(fā)我有的東西!');
return false;
}
},
remove:function (type,func) {
try{
let target = eventOb.handleFunc[type];
let index=target.indexOf(func);
if(index===-1)throw error;
target.splice(index,1);
}catch (e){
console.error('別老想搞什么飛機(jī),刪除我有的東西!');
}
},
once:function (type,func) {
eventOb.fire(type, func)?
eventOb.remove(type, func):null;
}
};
ES6
ES6不會(huì)也這么一大堆吧。。。
首先還是說為什么要用ES6.。。。
上面的一個(gè)實(shí)例我們發(fā)現(xiàn)是不能繼承的,只有一個(gè)實(shí)例,我們要用ES5把它改造成一個(gè)可以繼承的函數(shù)的話(并且使用最佳的組合繼承模式),首先要把handleFunc掛載在this上,然后把方法掛載在eventOb的prototype上,使用es6則更顯而易見。
class eventObs {
constructor(){
this.handleFunc={}
}
add(type,func){
if(this.handleFunc[type]){
if(this.handleFunc[type].indexOf(func)===-1){
this.handleFunc[type].push(func);
}
}else{
this.handleFunc[type]=[func];
}
};
fire(type,func){
try{
if(arguments.length===1) {
let target = this.handleFunc[type];
let count = target.length;
for (var i = 0; i < count; i++) {
target[i]();
}
}else{
let target = this.handleFunc[type];
let index=target.indexOf(func);
if(index===-1)throw error;
func();
}
return true;
}catch (e){
console.error('別老想搞什么飛機(jī),觸發(fā)我有的東西!');
return false;
}
};
remove(type,func){
try{
let target = this.handleFunc[type];
let index=target.indexOf(func);
if(index===-1)throw error;
target.splice(index,1);
}catch (e){
console.error('別老想搞什么飛機(jī),刪除我有的東西!');
}
};
once(type,func) {
this.fire(type, func)?
this.remove(type, func):null;
}
}