《javascript设计模式与开发实践》读书笔记(4)

本次笔记记录本周学习的迭代器模式和发布-订阅模式。

迭代器模式

定义:提供一种方式顺序访问一个聚合对象中的各个元素,而又不需要暴露该对象的内部表示。迭代器模式把迭代的过程分离出来,不用关系对象内部构造。javascript中的forEach就是一个迭代器。

一种迭代器的实现

var each = function(arr, callback){
    for(var i = 0, l = arr.length; i < l; i++){
        callback.call(arr, i, arr[i])
    }
}
each([1,2,3], function(i, n){
    alert(i,n)
})

内部迭代器和外部迭代器

内部迭代器就如上面的代码,each函数内部定义好了迭代规则,外部一次调用,就会完成整个迭代过程。外部迭代器要求必须显示的请求迭代下一个元素,这增加了调用的复杂度,但增加了迭代器的灵活性,可以控制迭代过程。如下是外部迭代器的实现:

var Iterator = function(obj){
    var current = 0
    var next = function(){
        current += 1
    }
    var isDone = function(){
        return current >= obj.length
    }
    var getCurrItem = function(){
        return obj[current]
    }
    return {
        next: next,
        isDone: isDone,
        getCurrItem: getCurrItem,
        length: obj.length
    }   
}

必须显示的调用iterator.next()来进行下一个元素的迭代。

迭代类数组对象和字面量对象

例如jquery中的each函数:

$.each = function(obj, callback){
    var value, i = 0, length = obj.length, isArray = isArraylike(obj)
    if(isArray){
        for(; i < length; i++){
            value = callback.call(obj, i, obj[i])
            if(value === false){
                break
            }
        }
    }else{
        for(i in obj){
            value = callback.call(obj, i, obj[i])
            if(value === false){
                break
            }
        }
    }
    return obj
}

总结

迭代器模式很容易理解,就是将迭代与业务逻辑分开。

发布-订阅模式

定义:又叫观察者模式,它定义对象间的一种一对多的依赖关系,当一个对象的状态发生该拜年时,所有依赖于它的对象都将得到通知。在js中,我们一般用事件模型来替代发布-订阅模式。

发布-订阅模式让两个对象松耦合地联系在一起,在新订阅者出现时,发布者的代码不需要任何修改;发布者需要改变时,也不会影戏之前的订阅者。

js中DOM事件就用到的发布-订阅模式。下面的代码就是订阅了body上的click事件,当body被点击后,会向订阅者发布这个消息。

document.body.addEventListener('click', fucntion(){
    alert(2)
})
document.body.click()

自定义事件

思路:定义发布者,把回调函数存放在发布者中的缓存列表,当发布消息的时候,发布者会遍历这个缓存列表,依次触发里面存放的回调函数。

var salesOffices = {} //发布者
salesOffices.clientList = {}  //缓存列表
salesOffices.listen = function(key, fn){
    if(!this.clientList[key]){
        this.clientList[key] = []
    }
    this.clientList[key].push(fn)
}
salesOffices.trigger = function(){
    var key = Array.prototype.shift.call(arguments)
    fns = this.clientList[key]
    if(!fns || fns.length === 0){
        return false
    }
    for(var i = 0, fn; fn = fns[i++];){
        fn.apply(this, arguments)
    }
}
salesOffices.listen('squareMeter88', function(price){
    console.log('price: ' + price)
})
salesOffices.trigger('squareMeter88', 20000)

取消订阅事件

如果要取消已经订阅的事件,需要在发布者对象中添加remove方法,用来取消订阅,接着上面的例子,代码如下:

salesOffices.remove = function(key, fn){
    var fns = this.clientList[key]
    if(!fns || fns.length === 0){
        return false
    }
    if(!fn){
        fns && (fns.length = 0) //如果没有具体回调函数,则取消所有订阅消息
    }else{
        for(var l = fns.length - 1; l >= 0; l--){
            if(fns[l] === fn){
                fns.splice(l, 1)    //反向遍历,用splice不会出问题
            }
        }
    }
}

先发布再订阅

类似于离线消息,效果如下:

Event.trigger('click', 1)
Event.listen('click', function(a){
    console.log(a)
})

具体实现思路是建立一个存放离线事件的堆栈,在事件发布的时候,如果还没有订阅这个事件,则暂时把这些动作包装在一个函数中,把包装函数存入堆栈,等到有对象来定于此事件,就便利堆栈并且依次执行这些包装函数。这里的代码比较复杂,包括解决全局命名冲突的问题,后续仔细研究。

总结

发布-订阅模式对对象之间进行了解耦,使发布者和订阅者逻辑分开,但是如果过度使用,对象与对象之间的必要联系会被深埋在背后,导致程序难以跟踪维护。并且发布-订阅者模式会消耗一定的内存和时间。

#笔记##读书笔记##设计#
全部评论

相关推荐

评论
点赞
收藏
分享

创作者周榜

更多
牛客网
牛客网在线编程
牛客网题解
牛客企业服务