深入理解JavaScript系列(44):设计模式之桥接模

介绍

桥接模式(Bridge)将抽象部分与它的实现部分分离,使它们都可以独立地变化。

正文

桥接模式最常用在事件监控上,先看一段代码:

双击代码全选

1

2

3

4

5

6

7

8

addEvent(element, 'click', getBeerById);

function getBeerById(e) {

var id = this.id;

asyncRequest('GET', 'beer.uri?id=' + id, function(resp) {

// Callback response.

console.log('Requested Beer: ' + resp.responseText);

});

}

 

上述代码,有个问题就是getBeerById必须要有浏览器的上下文才能使用,因为其内部使用了this.id这个属性,如果没用上下文,那就歇菜了。所以说一般稍微有经验的程序员都会将程序改造成如下形式:

双击代码全选

1

2

3

4

5

6

7

function getBeerById(id, callback) {

// 通过ID发送请求,然后返回数据

asyncRequest('GET', 'beer.uri?id=' + id, function(resp) {

// callback response

callback(resp.responseText);

});

}

 

实用多了,对吧?首先ID可以随意传入,而且还提供了一个callback函数用于自定义处理函数。但是这个和桥接有什么关系呢?这就是下段代码所要体现的了:

双击代码全选

1

2

3

4

5

6

addEvent(element, 'click', getBeerByIdBridge);

  function getBeerByIdBridge (e) {

    getBeerById(this.id, function(beer) {

      console.log('Requested Beer: '+beer);

  });

}

 

这里的getBeerByIdBridge就是我们定义的桥,用于将抽象的click事件和getBeerById连接起来,同时将事件源的ID,以及自定义的call函数(console.log输出)作为参数传入到getBeerById函数里。
这个例子看起来有些简单,我们再来一个复杂点的实战例子。

实战XHR连接队列

我们要构建一个队列,队列里存放了很多ajax请求,使用队列(queue)主要是因为要确保先加入的请求先被处理。任何时候,我们可以暂停请求、删除请求、重试请求以及支持对各个请求的订阅事件。

基础核心函数

在正式开始之前,我们先定义一下核心的几个封装函数,首先第一个是异步请求的函数封装:

双击代码全选

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

var asyncRequest = (function () {

function handleReadyState(o, callback) {

var poll = window.setInterval(

function () {

if (o && o.readyState == 4) {

                            window.clearInterval(poll);

if (callback) {

                                callback(o);

                            }

                        }

                    },

                    50

                    );

    }

 

var getXHR = function () {

var http;

try {

            http = new XMLHttpRequest;

            getXHR = function () {

returnnew XMLHttpRequest;

            };

        }

 

catch (e) {

var msxml = [

                        'MSXML2.XMLHTTP.3.0',

                        'MSXML2.XMLHTTP',

                        'Microsoft.XMLHTTP'

                        ];

 

for (var i = 0, len = msxml.length; i < len; ++i) {

try {

                    http = new ActiveXObject(msxml[i]);

                    getXHR = function () {

returnnew ActiveXObject(msxml[i]);

                    };

break;

                }

catch (e) { }

            }

        }

return http;

    };

 

returnfunction (method, uri, callback, postData) {

var http = getXHR();

        http.open(method, uri, true);

        handleReadyState(http, callback);

        http.send(postData || null);

return http;

    };

})();

 

上述封装的自执行函数是一个通用的Ajax请求函数,相信属性Ajax的人都能看懂了。
接下来我们定义一个通用的添加方法(函数)的方法:

双击代码全选

1

2

3

4

Function.prototype.method = function (name, fn) {

this.prototype[name] = fn;

returnthis;

};

 

最后再添加关于数组的2个方法,一个用于遍历,一个用于筛选:

双击代码全选

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

if (!Array.prototype.forEach) {

    Array.method('forEach', function (fn, thisObj) {

var scope = thisObj || window;

for (var i = 0, len = this.length; i < len; ++i) {

            fn.call(scope, this[i], i, this);

        }

    });

}

 

if (!Array.prototype.filter) {

    Array.method('filter', function (fn, thisObj) {

var scope = thisObj || window;

var a = [];

for (var i = 0, len = this.length; i < len; ++i) {

if (!fn.call(scope, this[i], i, this)) {

continue;

            }

            a.push(this[i]);

        }

return a;

    });

}

 

因为有的新型浏览器已经支持了这两种功能(或者有些类库已经支持了),所以要先判断,如果已经支持的话,就不再处理了。

观察者系统

观察者在队列里的事件过程中扮演着重要的角色,可以队列处理时(成功、失败、挂起)订阅事件:

双击代码全选

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

window.DED = window.DED || {};

DED.util = DED.util || {};

DED.util.Observer = function () {

this.fns = [];

}

 

DED.util.Observer.prototype = {

    subscribe: function (fn) {

this.fns.push(fn);

    },

 

    unsubscribe: function (fn) {

this.fns = this.fns.filter(

function (el) {

if (el !== fn) {

return el;

                }

            }

            );

            },

    fire: function (o) {

this.fns.forEach(

function (el) {

                el(o);

            }

            );

    }

};

 

队列主要实现代码

首先订阅了队列的主要属性和事件委托:

双击代码全选

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

DED.Queue = function () {

// 包含请求的队列.

this.queue = [];

// 使用Observable对象在3个不同的状态上,以便可以随时订阅事件

this.onComplete = new DED.util.Observer;

this.onFailure = new DED.util.Observer;

this.onFlush = new DED.util.Observer;

 

// 核心属性,可以在外部调用的时候进行设置

this.retryCount = 3;

this.currentRetry = 0;

this.paused = false;

this.timeout = 5000;

this.conn = {};

this.timer = {};

};