JavaScript设计模式(10)-观察者模式

观察者模式

1. 介绍

  • 发布者与订阅者是多对多的方式
  • 通过推与拉获取数据:发布者推送到订阅者或订阅者到发布者那边拉
  • 使并行开发的多个实现能彼此独立地进行修改
  • 其实我们在前端开发中使用到的时间监听就是浏览器实现的观察者模式

2. 示例:订阅报纸

function Publisher() {
    this.subscribers = [];
}
Publisher.prototype.deliver = function(data) {
    this.subscribers.forEach(function(fn) {
        fn(data)
    })
    return this;
}

Function.prototype.subscribe = function(publisher) {
    var that = this;
    var aleradyExists = publisher.subscribers.some(function(el) {
        return el === that;
    });
    if(!aleradyExists) {
        publisher.subscribers.push(this);
    }
    return this;
}

Function.prototype.unsubscribe = function(publisher) {
    var that = this;
    publisher.subscribers = publisher.subscribers.filter(function(el) {
        return el !== that;
    })
    return this
}

var NewYoukTimes = new Publisher;
var SfChronicle = new Publisher;

var Joe = function(from) {
    console.log('Delivery from ' + from + ' to Joe')
}

var Same = function(from) {
    console.log('Delivery from ' + from + ' to Same')
}

// usage

// 订阅
Joe.subscribe(NewYoukTimes).subscribe(SfChronicle)
Same.subscribe(NewYoukTimes)

// 发布
NewYoukTimes.deliver('aa').deliver('cc')
SfChronicle.deliver('dd')

// 取消订阅
Joe.unsubscribe(NewYoukTimes)

3. 示例:动画

里面有个 Animation 类提供了三个可以被订阅的观察对象 onStart、onComplete、onTween,有点类似于浏览器提供的事件监听,只是浏览器提供的是 onclick、onchange 等。由 Animation 内部决定这三个观察对象什么时候发布信息,其他的订阅者只要等待被触发就行。

<style>
    #img {
        margin-top: 20px;
    }
</style>

<body>
    <button id="startBt">开始</button>
    <button id="stopBt">停止</button>
    <br/>
    <img id="img" src="./images/fly1.png">
</body>

<script>
    Function.prototype.method = function(name, fn) {
        this.prototype[name] = fn
        return this
    }

    function Publisher() {
        this.subscribers = [];
    }
    Publisher.prototype.deliver = function(data) {
        this.subscribers.forEach(function(fn) {
            fn(data)
        })
        return this;
    }

    Function.prototype.subscribe = function(publisher) {
        var that = this;
        var aleradyExists = publisher.subscribers.some(function(el) {
            return el === that;
        });
        if(!aleradyExists) {
            publisher.subscribers.push(this);
        }
        return this;
    }

    Function.prototype.unsubscribe = function(publisher) {
        var that = this;
        publisher.subscribers = publisher.subscribers.filter(function(el) {
            return el !== that;
        })
        return this
    }

    var Animation = function(o) {
        this.config = o
        this.onStart = new Publisher   // 可观察对象
        this.onComplete = new Publisher  // 可观察对象
        this.onTween = new Publisher   // 可观察对象
        this.timer = undefined;
        this.frame = 1;
        this.state = 'stop'
    };
    Animation.method('start', function(){
        if(this.state !== 'stop') {
            return
        }
        this.state = 'start'
        this.onComplete.deliver('start at: ' + new Date().getTime())
        var that = this
        this.timer = setInterval(function(){
            that.onTween.deliver(that.frame)
            that.frame++;
        },this.config.timeLate);
    }).method('stop', function() {
        if(this.state !== 'start') {
            return
        }
        this.state = 'stop'
        clearInterval(this.timer);
        // this.frame = 1;
        this.onComplete.deliver('stop at: ' + new Date().getTime())
    })

    var animation = new Animation({ timeLate: 400})


    // 超人飞行部分动画
    var frameImg = document.getElementById('img')
    var jumpToFly = function(info) {        // 订阅者
        console.log(info)
    }
    var superManFlying = function(i) {
        var frame =  i%3 + 1;
        frameImg.src = "./images/fly" + frame + ".png"
        console.log('frame: ' + i)   // 订阅者
    };
    var downToLand = function(info) {       // 订阅者
        console.log(info)
    }
    jumpToFly.subscribe(animation.onStart)
    superManFlying.subscribe(animation.onTween)
    downToLand.subscribe(animation.onComplete)


    document.getElementById('startBt').addEventListener('click', function() {
        animation.start();
    })
    document.getElementById('stopBt').addEventListener('click', function() {
        animation.stop()
    })

</script>

4. 观察者的适用场合

  • 希望把人的行为和应用程序的行为分开

5. 观察者模式的利与弊

  • 利:
    • 在基于行为的大型应用程序中,可能会连续的发生几十、几百、几千次各种事件,通过观察者模式可以削减为事件注册注册监听器的次数,让可观察对象借助一个事件监听器替你处理各种行为并将信息委托给他的订阅者,从而减低内存消耗和互动性能,这样有利于减少系统开销并提高程序的可维护性
  • 弊:
    • 创建可观察对象所带来的加载事件开销(可以通过惰性加载技术化解,具体而言就是把新的可观察对象的实例化推迟到需要发送事件通知的时候,这样一来,订阅者在事件尚未创建的时候就能订阅他,而程序的初始化时间也就不收影响)

注意

转载、引用,但请标明作者和原文地址

原文地址:https://www.cnblogs.com/CccZss/p/8493599.html