本文参加了由公众号@若川视野 发起的每周源码共读活动,点击了解详情一起参与。
本期共读的两个模块mitt和tiny-emitter是对观察者模式的实现。
##首先来看看这个源码长什么样子
(function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g.TinyEmitter = f()}})(function(){var define,module,exports;return (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(require,module,exports){ },{}]},{},[1])(1)});
不懂就问,上面这个是什么东西?
1.什么是模块化?——避免变量污染和命名冲突的容器,并提供对外接口。
2.有啥好处?——拥有独立作用域,避免变量污染、命名冲突 - 降低项目复杂度,提高开发效率 - 提升代码可复用性和可维护性。
3.1 AMD
AMD(Asynchronous Module Definition),异步模块定义。在加载模块以及模块所依赖的其它模块时,AMD都采用异步加载的方式,避免模块加载阻塞网页渲染。
AMD作为一个规范,只需定义其语法API,而不关心其实现。AMD规范简单到只有一个API,即模块定义define函数:
define(name?, [dependencies]?, factory) - name是模块的标识,如果未提供则以文件名为标识; - dependencies表示所依赖的模块; - factory是模块初始化要执行的函数或对象,如果是函数,只执行一次,如果是对象,即为模块输出值。
AMD规范主要是针对前端浏览器的规范。
3.2 Commonjs
CommonJS规范规定,每个模块内部,module变量代表当前模块。这个变量是一个对象,它的exports属性(即module.exports)是对外的接口。加载某个模块,其实是加载该模块的module.exports属性。
CommonJS规范加载模块是同步加载的,也就是说,只有加载完成,才能执行后面的操作。
模块定义语法: module.exports=factory()
CommonJS主要是针对浏览器之外的js环境的规范。
这是一个匿名自执行函数,这个匿名函数拥有独立的作用域,既避免污染外界代码,也避免被外界代码污染。
展开第一个function可以看到如下代码:
typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
typeof define === 'function' && define.amd ? define(factory) :
(global = global || self, global.Vue = factory());
这段代码的作用是检测当前的运行环境支持哪种模块化规范,然后将Vue模块化,其中exports是Commonjs规范,define是AMD的规范。
在源码中可以看到,factory()最后return的是一个Vue对象。
当检测出为Commonjs规范时,导出Vue对象的写法为:
module.exports = factory();
当检测出为AMD规范时,写法为:
define(factory)。
为了更直观,改写一下这段代码:
if(typeof exports === 'object' && typeof module !== 'undefined'){
// Commonjs规范
module.exports = factory();
}else if(typeof define === 'function' && define.amd){
// AMD规范
define(factory);
}else{
// 其他情况下,将Vue挂载到全局对象中
// 在浏览器里就是给Window对象添加Vue属性,属性值为factory()返回的Vue对象
global = global || self,
global.Vue = factory();
}
我们回到主题让我们来试验下 tiny-emitter 怎么用
要不先简单理解下发布订阅吧,容我去找找
- 啥叫订阅 ?举个例子就是 vue 当中一个button绑定 @subscribe="subscribeDeal" 事件,另一种写法是
v-on:click大家想必不陌生吧,这里找个on 大家就可以理解为订阅啦,只不过on的这个时间不叫click叫subscribe,subscribe这个单词就是订阅的意思。 - 谁特么是发布 ? 继续看vue例子,上面我们订阅了一个叫subscribe事件,那么我们发布是不是
this.$emit("subscribe",value)就可以不停发布了?
methods:{
triggerSend(){this.$emit("subscribe",value)}
}
- 发布订阅好理解,但是什么是调度中心呢?我们直接看源码吧
tiny-emitter源码解析
通过实例化E类来使用
function E () {}
E.prototype = {
// 订阅如何实现?
on: function (name, callback, ctx) {
// 创建变量e,即事件调度中心,通过普通对象存储
var e = this.e || (this.e = {});
// 存储订阅事件
(e[name] || (e[name] = [])).push({
fn: callback,
ctx: ctx
});
return this;
},
// 单次订阅,有没有想起vue的修饰符once 例: click.self.once
once: function (name, callback, ctx) {
var self = this;
function listener () {
// 主要实现逻辑就是在触发事件的时候,取消当前订阅
self.off(name, listener);
callback.apply(ctx, arguments);
};
listener._ = callback
// 调用on订阅事件
return this.on(name, listener, ctx);
},
// 发布
emit: function (name) {
// 获取callback函数的运行参数
var data = [].slice.call(arguments, 1);
var evtArr = ((this.e || (this.e = {}))[name] || []).slice();
var i = 0;
var len = evtArr.length;
for (i; i < len; i++) {
// 遍历执行订阅的函数,并传递实参
evtArr[i].fn.apply(evtArr[i].ctx, data);
}
return this;
},
// 取消订阅
off: function (name, callback) {
var e = this.e || (this.e = {});
var evts = e[name];
// 存储订阅事件,支持定向取消订阅事件
var liveEvents = [];
if (evts && callback) {
for (var i = 0, len = evts.length; i < len; i++) {
if (evts[i].fn !== callback && evts[i].fn._ !== callback)
liveEvents.push(evts[i]);
}
}
// 如果存在其他订阅事件,则修改原存储的事件,否则直接删掉原存储事件
(liveEvents.length)
? e[name] = liveEvents
: delete e[name];
return this;
}
};