React / Vue 不同组件之间是如何通信的?
Vue
- 父子组件用props通信,子调用$emit,父监听
- 非父子组件用Event Bus通信
- 如果项目够复杂,可能需要Vuex等全局状态管理库通信
React
- 父子组件,父 -> 子 直接通过props,子 -> 父 通过callback回调
- 非父子组件,用发布订阅模式的Event模块
- 项目复杂用Redux、Mobx等全局状态管理库
- 用新的Context API
Event Bus是发布订阅模式的典型应用。很多模块的通信都是基于类似模式,包括安卓开发中的Event Bus,Node.js中的Event模块(Node中几乎所有模块都依赖于Event,包括但不限于http、stream、buffer及fs等模块)
这里我们没有对传入的参数进行判断校验,仅仅对核心方法进行实现
1. 基本构造
1.1 初始化class
这里选择Map作为存储事件的结构,因为作为键值对的存储方式Map比一般对象更加适合,操作起来也更加简介
class EventEmitter {
constructor() {
this._events = this._events || new Map() // 存储事件 / 回调键值对
this._maxListeners = this._maxListeners || 10 // 设置监听上限
}
}1.2 监听与触发
触发监听函数我们可以用apply与call两种方法,在少数参数时call的性能更好,多个参数时apply性能更好
// 触发名为type的事件
class EventEmitter {
constructor() {
this._events = this._events || new Map() // 存储事件 / 回调键值对
this._maxListeners = this._maxListeners || 10 // 设置监听上限
}
emit(type, ...args) {
let handler
// 从存储事件键值对的 this._events 中获取对应事件回调函数
handler = this._events.get(type)
if (args.length > 0) {
handler.apply(this, args)
} else {
handler.call(this)
}
return
}
addListener(type, fn) {
// 将type事件以及对应的fn函数放入 this._events 中存储
if (!this._events.get(type)) {
this._events.set(type, fn)
}
}
}
// 我们实现了触发事件的emit方法和监听事件的addListener方法, 至此就可以进行简单实践了
// 实例化
const emitter = new EventEmitter()
// 监听一个名为sayHello的事件
emitter.addListener('sayHello', name => {
console.log(`hello, ${name}`)
})
// 触发事件,执行回调
emitter.emit('sayHello', 'fridolph') // hello, fridolph以上简单实现了基本的监听/触发,但是若有多个监听者,触发同一事件,则只会触发第一个,所以我们还需继续完善。
2. 完善
2.1 监听/触发器升级
我们的addListener实现方法还不够健全,在绑定第一个监听者之后,我们就无法对后续监听者进行绑定了,因此我们需要将后续监听者与第一个监听者函数放到一个数组里
// 省略
emit(type, ...args) {
let handler
handler = this._events.get(tyep)
// 如果是一个数组说明有多个监听者,需要依次触发里面的函数
if (Array.isArray(handler)) {
for (let i = 0; i < handler.length; i++) {
if (args.length > 0) {
handler[i].apply(this, args)
} else {
handler[i].call(this)
}
}
} else {
// 单个函数的情况我们直接触发即可
if (args.length > 0) {
handler.apply(this, args)
} else {
handler.call(this)
}
}
return
}
// 监听名为type的事件
addListener(type, fn) {
// 获取对应事件名称的函数清单
const handler = this._events.get(type)
if (!handler) {
this._events.set(type, fn)
} else if (handler && typeof handler === 'function') {
// 如果handler是函数说明只有一个监听者
// 多个监听者我们需要用数组存储
this._events.set(type, [handler, fn])
} else {
// 已经有多个监听者, 那么直接往数组里push函数即可
handler.push(fn)
}
}2.2 移除监听
我们会用removeListener函数移除监听函数,但是匿名函数是无法移除的
removeListener(type, fn) {
// 获取对应事件名称的函数清单
const handler = this._events.get(type)
// 如果是函数,说明只被监听了一次
if (handler && typeof handler === 'function') {
this._events.delete(type, fn)
} else {
let position
// 如果handler是数组,说明被监听多次要找到对应的函数
for (let i = 0; i < handler.length; i++) {
if (handler[i] === fn) {
position = i
} else {
position = -1
}
}
// 如果找到匹配的函数,从数组中清除
if (position !== -1) {
// 找到数组对应的位置,直接清除此回调
handler.splice(position, 1)
// 如果清除后只有一个函数,那么取消数组,以函数形式保存
if (handler.length === 1) {
this._events.set(type, handler[0])
}
} else {
return this
}
}
}3.0 发现问题
我们已经基本完成了Event最重要的几个方法,也完成了升级改造。可以说一个Event的骨架已被开发好,但是还有几个不足和需要补充的
- 鲁棒性不足:我们没有对参数进行充分的判断,没有完善的报错机制
- 模拟不够充分,除了
removeAllListeners这些方法没有实现以外,例如监听时间后会触发newListener事件,我们也没有实现,另外最开始的监听者上限我们也没有利用到
“鲁棒性”是指控制系统在一定(结构,大小)的参数摄动下,维持其它某些性能的特性。也就是系统的健壮性,是在异常和危险情况下系统生存的关键。鲁棒控制方法适用于稳定性和可靠性作为首要目标的应用,同时过程的动态特性已知且不确定因素的变化范围可以预估。
最终参考 event.js 完整的Event实现
Reflect 是ES6新增内置对象,它提供拦截JS操作的方法。这些方法与处理器对象的方法相同。Reflect不是一个函数对象,因此它是不可构造的,其所有属性和方法都是静态的(类比Math的方法)
// 开头是copyright 版权信息等 直接省略
'use strict';
// 定义R对象 如果支持es6用原生的Reflect,没就相当于提供polyfill
var R = typeof Reflect === 'object' ? Reflect : null
var ReflectApply = R && typeof R.apply === 'function'
? R.apply
: function ReflectApply(target, receiver, args) {
return Function.prototype.apply.call(target, receiver, args)
}
var ReflectOwnKeys
if (R && typeof R.ownKeys === 'function') {
ReflectOwnKeys = R.ownKeys
} else if (Object.getOwnPropertySymbols) {
ReflectOwnKeys = function ReflectOwnKeys(target) {
return Object.getOwnPropertyNames(target)
.concat(Object.getOwnPropertySymbols(target))
}
} else {
RelectOwnKeys = function ReflectOwnKeys(target) {
return Object.getOwnPropertyNames(target)
}
}
// console.warn的封装
function processEmitWarning(warning) {
if (console && console.warn) console.warn(warning)
}
// Number.isNaN 是es6加入的,后面是并联的兼容处理,Number类型 自身与自身不相等的只有NaN了
var NumberIsNaN = Number.isNaN || function NumberIsNaN(value) {
return value !== value
}
function EventEmitter() {
EventEmitter.init.call(this)
}
// 将EventEmitter构造函数作为默认导出
module.exports = EventEmitter
// 向后兼容node 0.10.x 版本 看来这个库相当老了啊,不过也不妨碍我们学习
EventEmitter.EventEmitter = EventEmitter
EventEmitter.prototype._events = undefined
EventEmitter.prototype._eventsCount = 0
EventEmitter.prototype._maxListeners = undefined
// 默认情况下,如果超过10个侦听器,EventEmitters将会输出警告添加到它
// 这是一个有用的默认值,它有助于查找内存泄漏
var defaultMaxListeners = 10
// 这个API见得还是挺多了,EventEmitter.defaultMaxListeners 可以直接获取默认最大侦听器数,通过传参 可设置最大数
Object.defineProperty(EventEmitter, 'defaultMaxListeners', {
enumerable: true,
get: function() {
return defaultMaxListeners
},
set: function(arg) {
if (typeof arg !== 'number' || arg < 0 || NumberIsNaN(arg)) {
throw new RangeError('The value of "defaultMaxListeners" is out of range. It must be a non-negative number. Received ' + arg + '.')
}
defaultMaxListeners = arg
}
})
// 构造类的静态方法
EventEmitter.init = function() {
// 相当于constructor的构造函数调用 初始化
if (this._events === undefined || this._events === Object.getPrototypeOf(this)._events) {
this._events = Object.create(null)
this._eventsCount = 0
}
this._maxListeners = this._maxListeners || undefined
}
// 显然不是所有的Emitters侦听器都应该限制为10个。这个功能允许增加
EventEmitter.prototype.setMaxListeners = function setMaxListeners(n) {
if (typeof n !== 'number' || n < 0 || NumberIsNaN(n)) {
throw new RangeError('The value of "n" is out of range. It must be a non-negative number. Received ' + n + '.')
}
this._maxListeners = n
return this
}
// 给下面的 getMaxListeners方法用 EventEmitter原型方法里的 this指向实例对象
// 所以可从实例中 拿到 属性值 _maxListeners
function $getMaxListeners(that) {
if (that._maxListeners === undefined) {
return EventEmitter.defaultMaxListeners
}
return that._maxListeners
}
EventEmitter.prototype.getMaxListeners = function getMaxListeners() {
return $getMaxListeners(this)
}
EventEmitter.prototype.emit = function emit(type) {
var args = []
// emit第一个参数一般是方法名,所以 从arguments[1]开始
for (var i = 1; i < arguments.length; i++) args.push(arguments[i])
// 右边的表达式返回一个布尔值 ,type报错就为true。doError可理解为错误标记的flag
var doError = (type === 'error')
// 拿到 实例的 _events 对象
var events = this._events
if (events !== undefined) {
doError = (doError && events.error === undefined)
// 没有错就直接返回 false
} else if (!doError) return false
// 下面是错误处理
if (doError) {
var er
if (args.length > 0) {
er = args[0]
}
if (er instanceof Error) {
//注意:“throw”这一行的注释是有意的,他们表示 如果这导致未处理的异常,则在Node的输出中输入。
throw er
}
var err = new Error('Unhandled error.' + (er ? ' (' + er.message + ')' : ''))
// 错误上下文为er 这里的er就是传进来的type的报错环境上下文
err.context = er
// 抛出错误
throw err
}
// 从events里找type对应的函数 拿到 并赋给handler
var handler = events[type]
if (handler === undefined) return false
if (typeof handler === 'function') {
// 绑定上下文
ReflectApply(handler, this, args)
} else {
// handler多次调用 把多个handler 放到数组里来处理
var len = handler.length
var listeners = arrayClone(handler, len)
for (var i = 0; i < len; i++) {
ReflectApply(listeners[i], this, args)
}
}
return true
}
function _addListener(target, type, listener, prepend) {
var m
var events
var existing
// listener类型判断
if (typeof listener !== 'undefined') {
throw new TypeError('The "listener" argument must be of type Function. Received type' + typeof listener)
} else {
// 为了避免在 type === "newListener" 这种情况下递归
// 在将其添加到侦听器之前,首先触发 "newListener"
if (events.newListener !== undefined) {
target.emit('newListener', type, listener.listener ? listener.listner : listener)
// 重新分配 `events`,因为newListener处理程序可能导致了 this._events被分配给一个新的对象
events = target._events
}
existing = events[type]
}
if (existing === undefined) {
// 优化一个listener的情况。不需要额外的数组对象
existing = events[type] = listener
++target._eventsCount
} else {
if (typeof exiting === 'function') {
// 添加第二个元素,需要更改为数组
existing = events[type] = prepend ? [listener, existing] : [existing, listener]
//如果我们已经有了一个数组,只需追加
} else if (prepend) {
existing.unshift(listener)
} else {
existing.push(listener)
}
// 检查监听器泄漏
m = $getMaxListeners(target)
// m > 0 && (exiting.length > m && !existing.warned )
if (m > 0 && exiting.length > m && !existing.warned) {
existing.warned = true
// 没有错误代码,因为它是一个warning
var w = new Error(
'Possible EventEmitter memory leak detected. ' +
existing.length + ' ' + String(type) + ' listeners ' +
'added. Use emitter.setMaxListeners() to ' +
'increase limit'
)
w.name = 'MaxListenersExceededWarning'
w.emitter = target
w.type = type
w.count = existing.length
ProcessEmitWarning(w)
}
}
return target
}
// 结合上面的函数,这是高阶函数的用法 普通地,我们一般会传 事件名和回调
// 这里返回 _addListner方法 第一个参数是 this 实例对象作为上下文环境
// 然后是 事件名,回调,prepend默认为false
EventEmitter.prototype.addListener = function addListener(type, listener) {
return _addListener(this, type, listener, false)
}
// 同名方法 addListner 和用 on等效
EventEmitter.prototype.on = EventEmitter.prototype.addListener
// 和on的区别在于 prepend为true,当前已经是数组了才会这样用(不同我们来,已经封装在内部逻辑里了)
EvnetEmitter.prototype.prpendListener = function prependListener(type, listener) {
return _addListener(this, type, listener, true)
}
function onceWrapper() {
var args = []
for (var i = 0; i < arguments.length; i++) args.push(arguments[i])
if (!this.fired) {
this.target.removeListener(this.type, this.wrapFn)
this.fired = true
ReflectApply(this.listener,this.target, args)
}
}
function _onceWrap(target, type, listener) {
var state = {fired: false, wrapFn: undefined, target: target, type: type, listener: listener}
var wrapped = onceWrapper.bind(state)
wrapped.listener = listener
state.wrapFn = wrapped
return wrapped
}
EventEmitter.prototype.once = function once(type, listener) {
if (typeof listener !== 'function') {
throw new TypeError('The "listener" argument must be of type Function. Received type ' + typeof listener)
}
this.on(type, _onceWrap(this, type, listener))
return this
}
EventEmitter.prototype.prependOnceListener = function prependOnceListener(type, listener) {
if (typeof listener !== 'function') {
throw new TypeError('The "listener" argument must be of type Function. Received type ' + typeof listener)
}
this.prependListener(type, _onceWrap(this, type, listener))
return this
}
EventEmitter.prototype.removeListener = function removeListener(type, listener) {
var list, events, position, i, originalListener
if (typeof listener !== 'function') {
throw new TypeError('The "listener" argument must be of type Function. Received type ' + typeof listener)
}
events = this._events
if (events === undefined) return this
list = events[type]
if (list === undefined) return this
if (list === listener || list.listener === listener) {
if (--this._eventsCount === 0) {
this._events = Object.create(null)
} else {
delete events[type]
if (events.removeListener) {
this.emit('removeListener', type, list.listner || listener)
}
}
} else if (typeof list !== 'function') {
position = -1
for (i = list.length - 1; i >= 0; i--) {
originalListener = list[i].listener
position = i
break
}
if (position < 0) return this
if (position === 0) {
list.shift()
} else {
sliceOne(list, position)
}
if (list.length === 1) {
events[type] = list[0]
}
if (events.removeListener !== undefined) {
this.emit('removeListener', type, originalListener || listener)
}
return this
}
}
EventEmitter.prototype.off = EventEmitter.prototype.removeListener
EventEmitter.prototype.removeAllListeners = function removeAllListeners(type) {
var listeners, events, i
events = this._events
if (events === undefined) return this
// 不侦听removeListener,不需要触发emit
if (events.removeListener === undefined) {
if (arguments.length === 0) {
this._events = Object.create(null)
this._eventsCount = 0
} else if (events[type] !== undefined) {
if (--this._eventsCount === 0) {
this._events = Object.create(null)
} else {
delete events[type]
}
}
return this
}
// 为所有事件上的所有侦听器发出removeListener
if (arguments.length === 0) {
var keys = Object.keys(events)
var key
for (i = 0; i < keys.length; i++) {
key = keys[i]
if (key === 'removeListener') continue
this.removeAllListeners[key]
}
this.removeAllListeners('removeListener')
this._events = Object.create(null)
this._eventsCount = 0
return this
}
listeners = events[type]
if (typeof listeners === 'function') {
this.removeListener(type, listeners)
} else if (listeners !== undefined) {
// LIFO order
for (i = listeners.length - 1; i >= 0; i--) {
this.removeListener(type, listeners[i])
}
}
return this
}
function _listeners(target, type, unwrap) {
var events = target._events
if (events === undefined) return []
var evlistener = events[type]
if (evlistener === undefined) return []
if (typeof evlistener === 'function') {
return unwrap ? [evlistener.listener || evlistener] : [evlistener]
}
return unwrap ? unwrapListeners(evlistener) : arrayClone(evlistener, evlistener.length)
}
EventEmitter.prototype.listeners = function listeners(type) {
return _listeners(this, type, true)
}
EventEmitter.prototype.rawListeners = function rawListeners(type) {
return _listeners(this, type, false)
}
EventEmitter.listenerCount = function(emitter, type) {
if (typeof emitter.listenerCount === 'function') {
return emitter.listenerCount(type)
} else {
return listenerCount.call(emitter, type)
}
}
EventEmitter.prototype.listenerCount = listenerCount
function listenerCount(type) {
var events = this._events
if (events !== undefined) {
var evlistener = events[type]
if (typeof evlistener === 'function') {
return 1
} else if (evlistener !== undefined) {
return evlistener.length
}
}
return 0
}
EventEmitter.prototype.eventNames = function eventNames() {
return this._eventsCount > 0 ? ReflectOwnKeys(this._events) : []
}
function arrayClone(arr, n) {
var copy = new Array(n)
for (var i = 0; i < n; i++) {
copy[i] = arr[i]
}
return copy
}
function sliceOne(list, index) {
for (; index + 1 < list.length; index++) {
list[index] = list[index + 1]
}
list.pop()
}
function unwrapListeners(arr) {
var ret = new Array(arr.length)
for (var i = 0; i < ret.length; i++) {
ret[i] = arr[i].listener || arr[i]
}
}