addEventListener
jQuery
实现EventBus
它定义了一种一对多的关系,让多个订阅者对象同时监听一个发布者。这个发布者的状态发生变化时就会通知所有订阅自己的订阅者对象,使它们能够自动更新自己
我们关注了一个公众号。那么,关注这个公众号的所有人都会收到公众号的推送。
这就是一个典型的发布-订阅模式。我们订阅了公众号,公众号有消息发布出来,我们都会收到。这里,我们是订阅者,公众号是发布者。
addEventListener
window.addEventListener('load', function() {
console.log('load')
})
这段代码很常见,我们经常将一些操作挂载在onload
上,当页面元素资源加载完毕,就会触发onload
事件上的回调。
这也是一种发布-订阅模式,我们无法预知页面何时加载完毕,但可以通过window
的onload
事件,它会在加载完毕时向订阅者发布消息(即执行回调函数)
jQuery
自带的API( on
, trigger
, off
)来实现事件的订阅,发布,取消;
function eventHandler() {
console.log('您订阅的消息来喽!')
}
// 事件订阅
$('#app').on('myEvent', eventHandler)
// 发布
$('#app').trigger('myEvent')
// 输出:您订阅的消息来喽!
// 取消订阅
$('#app').off('myEvent')
$('#app').trigger('myEvent')
// 无输出
function eventHandler() {
console.log('您订阅的消息来喽!')
}
var app = document.getElementById('app')
// 事件订阅
app.addEventListener('myEvent', eventHandler)
// 发布
app.dispatchEvent(new Event('myEvent'))
// 输出:您订阅的消息来喽!
// 取消订阅
app.removeEventListener('myEvent',eventHandler)
app.dispatchEvent(new Event('myEvent'))
// 无输出
案例:一家零食小铺,“吃货们”发现“绝味鸭脖”等零食售罄了!于是留了个电话给店主,店主给记在小本本上,等有货的时候,再通过小本本打电话通知“吃货们”
class eventHandler{
constructor() {
this.list = {}
}
// 消息订阅
subscribe(type, fn) {
if(this.list[type]){
if(!this.list[type].includes(fn)){
this.list[type].push(fn)
}
}else{
this.list[type]=[fn]
}
}
// 消息退订
unsubscribe(type, fn) {
if(!this.list[type] || !this.list[type].includes(fn))return
let index = this.list[type].indexOf(fn)
this.list[type].splice(index,1)
}
// 消息发布
notify(type, info){
if(!this.list[type])return
this.list[type].forEach(fn => fn(info))
}
}
const shop = new eventHandler()
// 小白先生和大黄女士 预订绝味鸭脖
shop.subscribe('绝味鸭脖', message => console.log('小白先生——' + message))
shop.subscribe('绝味鸭脖', f1 = message => console.log('大黄女士——' + message))
// 翠花 预订泡椒凤爪
shop.subscribe('泡椒凤爪', message => console.log('翠花——' + message))
console.log('进货了,打电话通知买家:')
shop.notify('绝味鸭脖','绝味鸭脖进货了!')
shop.notify('泡椒凤爪','泡椒凤爪进货了!')
console.log('绝味鸭脖卖完了,通知买家:')
shop.notify('绝味鸭脖','绝味鸭脖卖完了!')
console.log('没买到鸭脖的大黄,愤怒的退订了')
shop.unsubscribe('绝味鸭脖', f1)
console.log('进货了,打电话通知买家:')
shop.notify('绝味鸭脖','绝味鸭脖进货了!')
EventBus
vue有父子组件通信,兄弟组件通信。
父子组件通信中:父组件通过props
向下传递数据给子组件,子组件可以通过$emit
事件通知父组件;
兄弟组件通信中:如果不使用Vuex
来共享数据,那我们可以使用vue中的事件总线EventBus
来通信
代码示例:
组件B发送消息给组件A
创建event-bus.js
文件:
// event-bus.js
import Vue from 'vue'
export const EventBus = new Vue()
组件A使用:
// 组件A
import { EventBus } from "./event-bus.js";
EventBus.$on("myevent", args => {
console.log(args)
})
组件B使用:
// 组件B
import { EventBus } from "./event-bus.js";
EventBus.$emit("myevent", 'some args')
vue利用发布-订阅模式来实现数据层和视图层的双向绑定
组件渲染函数(Component Render Function
)被执行前,会对数据层的数据进行响应式化。响应式化大致就是使用 Object.defineProperty
把数据转为 getter/setter
,并为每个数据添加一个订阅者列表的过程。这个列表是getter
闭包中的属性,将会记录所有依赖这个数据的组件。
每个组件都对应一个 Watcher
订阅者。当每个组件的渲染函数被执行时,都会将本组件的 Watcher 放到自己所依赖的响应式数据的订阅者列表里,这就相当于完成了订阅,一般这个过程被称为依赖收集(Dependency Collect
)
当响应式数据发生变化的时候,也就是触发了 setter
时,setter
会负责通知(Notify
)该数据的订阅者列表里的 Watcher
,Watcher
会触发组件重渲染(Trigger re-render
)来更新(update
)视图
vue源码如下,大致了解一下:
// src/core/observer/index.js
Object.defineProperty(obj, key, {
enumerable: true,
configurable: true,
get: function reactiveGetter () {
const value = getter ? getter.call(obj) : val // 如果原本对象拥有getter方法则执行
if (Dep.target) {
dep.depend() // 进行依赖收集,dep.addSub
if (childOb) {
childOb.dep.depend()
if (Array.isArray(value)) {
dependArray(value)
}
}
}
return value
},
set: function reactiveSetter (newVal) {
const value = getter ? getter.call(obj) : val
/* eslint-disable no-self-compare */
if (newVal === value || (newVal !== newVal && value !== value)) {
return
}
/* eslint-enable no-self-compare */
if (process.env.NODE_ENV !== 'production' && customSetter) {
customSetter()
}
// #7981: for accessor properties without setter
if (getter && !setter) return
if (setter) {
setter.call(obj, newVal) // 如果原本对象拥有setter方法则执行
} else {
val = newVal
}
childOb = !shallow && observe(newVal)
dep.notify() // 如果发生变更,则通知更新
}
})
上面的dep.depend()
和dep.notify()
是订阅和发布的具体方法
简单来说,响应式数据是消息的发布者,而视图层是消息的订阅者,如果数据更新了,那么发布者会发布数据更新的消息来通知视图更新,从而实现数据层和视图层的双向绑定。
优点:实现解耦
缺点: