观察者模式

概念

发布 & 订阅

一对多

示例

例如你在星巴克点了咖啡,此时你并不需要在吧台坐等,你只需要回到位子上玩手机,等咖啡好了服务员会叫你。不光叫你,其他人的咖啡好了,服务员也会叫他们来取。

还有,网页事件就是最常用的观察者模式

```html
<button id="btn1">btn</button>

<script>
    $('#btn1').click(function () {
        console.log(1)
    })
    $('#btn1').click(function () {
        console.log(2)
    })
    $('#btn1').click(function () {
        console.log(3)
    })
</script>
```

另外,JS 异步,只要用到 callback 函数,都是观察者模式

```js
setTimeout(function () {
    alert(100)
}, 1000)
```

UML类图

代码演示

// 主题,接收状态变化,触发每个观察者
class Subject {
    constructor() {
        this.state = 0
        this.observers = []
    }
    getState() {
        return this.state
    }
    setState(state) {
        this.state = state
        this.notifyAllObservers()
    }
    attach(observer) {
        this.observers.push(observer)
    }
    notifyAllObservers() {
        this.observers.forEach(observer => {
            observer.update()
        })
    }
}

// 观察者,等待被触发
class Observer {
    constructor(name, subject) {
        this.name = name
        this.subject = subject
        this.subject.attach(this)
    }
    update() {
        console.log(`${this.name} update, state: ${this.subject.getState()}`)
    }
}

// 测试代码
let s = new Subject()
let o1 = new Observer('o1', s)
let o2 = new Observer('o2', s)
let o3 = new Observer('o3', s)

s.setState(1)
s.setState(2)
s.setState(3)

使用场景

网页事件

<button id="btn1">btn</button>

<script>
    $('#btn1').click(function () {
        console.log(1)
    })
    $('#btn1').click(function () {
        console.log(2)
    })
    $('#btn1').click(function () {
        console.log(3)
    })
</script>

Promise

一开始说到异步有 callback 的都是观察者模式,而 Promise 作为异步的解决方案,也避免不了要使用。

```js
function loadImg(src) {
    var promise = new Promise(function (resolve, reject) {
        var img = document.createElement('img')
        img.onload = function () {
            resolve(img)
        }
        img.onerror = function () {
            reject('图片加载失败')
        }
        img.src = src
    })
    return promise
}

var src = 'https://www.imooc.com/static/img/index/logo_new.png'
var result = loadImg(src)
result.then(function (img) {
    console.log('width', img.width)
    return img
}).then(function (img) {
    console.log('height', img.height)
})
```

`resolve`和`reject`就相当于之前的`setState`,状态改变,其实这也是 Promise 的真实状态变化:pending -> fulfilled 或者 pending -> rejected 。两个`then`就是观察者,状态变化就会触发观察者`update` 。

jQuery callbacks

jQuery callbacks 是 jQuery 的内部底层功能,服务于对外的 API 如 ajax deferred 等。jQuery 这么通用的 lib 都有必要维护一个通用的观察者功能,可见观察者模式在 JS 中的应用之广泛

var callbacks = $.Callbacks() // 注意大小写
callbacks.add(function (info) {
    console.log('fn1', info)
})
callbacks.add(function (info) {
    console.log('fn2', info)
})
callbacks.add(function (info) {
    console.log('fn3', info)
})
callbacks.fire('gogogo')
callbacks.fire('fire')

nodejs 自定义事件

const EventEmitter = require('events').EventEmitter

const emitter1 = new EventEmitter()
emitter1.on('some', () => {
    // 监听 some 事件
    console.log('some event is occured 1')
})
emitter1.on('some', () => {
    // 监听 some 事件
    console.log('some event is occured 2')
})
// 触发 some 事件
emitter1.emit('some')

以上代码中,先引入 nodejs 提供的`EventEmitter`构造函数,然后初始化一个实例`emitter1`。实例通过`on`可监听事件,`emit`可以触发事件,事件名称可以自定义,如`some`。

自定义事件触发的时候还可传递参数,例如

const EventEmitter = require('events').EventEmitter
const emitter = new EventEmitter()
emitter.on('sbowName', name => {
    console.log('event occured ', name)
})
emitter.emit('sbowName', 'zhangsan')  // emit 时候可以传递参数过去

上文说到`EventEmitter`实例有`on`和`emit`接口,其实自定义 class 的实例也可以有,只不过需要继承`EventEmitter`。使用 ES6 的继承语法很容易实现

const EventEmitter = require('events').EventEmitter

// 任何构造函数都可以继承 EventEmitter 的方法 on emit
class Dog extends EventEmitter {
    constructor(name) {
        super()
        this.name = name
    }
}
var simon = new Dog('simon')
simon.on('bark', function () {
    console.log(this.name, ' barked')
})
setInterval(() => {
    simon.emit('bark')
}, 500)

和 jQuery callbacks 一样,自定义事件也是 nodejs 中底层、通用的功能,很多其他功能要继承`EventEmitter`以实现自定义事件功能,下文会讲到,也能看出观察者模式在 nodejs 中应用的广泛。

nodejs stream

stream 是 nodejs 的基础模块,就是把大数据(一次性读取内存放不开)的操作当做一个流,来一点一点的读取,直到读取完毕。

例如一个大文件,几百万行(一般是日志文件),想要得知它的字符长度,就需要用到 stream 。既然是一点一点的读取,那么每次读取一点就得知道读取的是什么,读取完毕也得得到通知,这就需要观察者模式。

// Stream 用到了自定义事件
var fs = require('fs')
var readStream = fs.createReadStream('./data/file1.txt')  // 读取文件的 Stream

var length = 0
readStream.on('data', function (chunk) {
    length += chunk.toString().length
})
readStream.on('end', function () {
    console.log(length)
})

nodejs 还专门指定了 readline ,跟上述的模式一样,只不过是一行一行读取文件,例如要知道上述文件一共有多少行,可以使用

// readline 用到了自定义事件
var readline = require('readline');
var fs = require('fs')

var rl = readline.createInterface({
    input: fs.createReadStream('./data/file1.txt')
});

var lineNum = 0
rl.on('line', function(line){
    lineNum++
});
rl.on('close', function() {
    console.log('lineNum', lineNum)
});

nodejs 中:处理 http 请求;多进程通讯

```js
var http = require('http')

function serverCallback(req, res) {
    var method = req.method.toLowerCase() // 获取请求的方法
    if (method === 'get') {
        console.log('get 请求不处理')
    }
    if (method === 'post') {
        // 接收 post 请求的内容
        var data = ''
        req.on('data', function (chunk) {
            // “一点一点”接收内容
            console.log('chunk', chunk.toString())
            data += chunk.toString()
        })
        req.on('end', function () {
            // 接收完毕,将内容输出
            console.log('end')
            res.writeHead(200, {'Content-type': 'text/html'})
            res.write(data)
            res.end()
        })
    }
    
}
http.createServer(serverCallback).listen(8081)  // 注意端口别和其他 server 的冲突
console.log('监听 8081 端口……')
```

使用 curl 模拟一下即可

```shell
curl -H "Content-Type:application/json" -X POST -d '{"user": "admin", "passwd":"12345678"}' http://127.0.0.1:8081/
```

## nodejs 多进程通讯

```js
// parent.js
var cp = require('child_process')
var n = cp.fork('./sub.js')
n.on('message', function (m) {
    console.log('PARENT got message: ' + m)
})
n.send({hello: 'workd'})

// sub.js
process.on('message', function (m) {
    console.log('CHILD got message: ' + m)
})
process.send({foo: 'bar'})
```

**同理于浏览器端的 webworker**

Vue 和 React 组件生命周期触发

vue React 使用组件化,每个组件实例都有固定的生命周期,生命周期的意思就是在实例运行的某个特定的节点,执行你要做的操作,例如`created`生命周期,打印一句话

new Vue({
  data: {
    a: 1
  },
  created: function () {
    // `this` 指向 vm 实例
    console.log('a is: ' + this.a)
  }
})

这些生命周期的函数,其实也都是观察者,当组件实例运行到某个阶段时,就会触发这个观察者。这是 vue 源码中的某个片段

function callHook (vm, hook) {
  var handlers = vm.$options[hook];  // 获取生命周期的所有函数
  if (handlers) {
    for (var i = 0, j = handlers.length; i < j; i++) {
      handlers[i].call(vm);  // 遍历,挨个触发
    }
  }
}

vm._self = vm;
initLifecycle(vm);
initEvents(vm);
initRender(vm);
callHook(vm, 'beforeCreate');  // 触发 beforeCreate 生命周期
initInjections(vm);
initState(vm);
initProvide(vm);
callHook(vm, 'created');  // 触发 created 生命周期

1

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值