利用微博信息上传demo推导观察者模式:
看一下大体实现功能效果:
首先此demo是一个多人开发的一个demo, 分析: a同学对应的功能是: 头部区域: 控制消息数量的增加与减少 b同学对应的功能是:
1.将信息渲染在li标签中
2.将li追加到ul中
3.点击span消失li标签
c同学对应的功能是:
获取用户输入的信息,
传递给b同学供其渲染,
并且调用a b同学的功能。
接下来进行推导:
当多人进行模块化开发的时候 如果利用全局变量 来让彼此访问彼此的功能 是会造成 全局污染的,所以此方法绝对不可行!
那么进阶方式是这样的: 就是利用一个对象来储存对应的方法 但是这种方式也是不可取的 因为全局当中存在一个对象是一个很危险的行为 随时都容易被修改
接下来可以通过闭包类的方式,将储存方法的对象,私有化。 下面是html和css代码,后面是具体使用闭包类的思路。 html代码:
消息数量: 0
- 你好,世界×
css:
- { margin: 0; padding: 0; list-style: none; } .container { width: 1120px; margin: 0 auto; } .content ul { height: 400px; } .content ul li { height: 20px; padding: 3px; overflow: hidden; } .content ul li:nth-child(odd) { background-color: orange; } .content ul li span { float: right; cursor: pointer; }
整体思路如下: 先看代码: //这是个全局中的的一个监听对象 var observer = (function() { var ob = {
};
return {
on:function(type,fn) {
ob[type] = fn;
},
trigger:function(type) {
ob[type]();
}
}
})()
复制代码
利用闭包类 首先存储一个对象这个对象用来存储功能 OB{ } 闭包类 返回一个接口对象 返回的这个对象用途就是为这个ob存储不同的功能,
想到功能就会出现函数,
因为函数具有参数以及封装语句的功能
Object[“type”]语法是的可以定义出不同函数
在此案例中定义了俩个函数
一个是 定义功能的on
以及一个调用功能的trigger
通过trigger调用函数的时候需要传入参数时: 就是需要传到方法定义的上面去此句话留作思考,后面可以解释
那么通过这个闭包类就可以通过observer这个对象定义功能,以及调用功能了,但是呢,这个observer存在全局中 任就不是一个很好的方式, 那么用什么方法呢?
模块化开发宗旨就是模块既文件,我们可以定义个工具模块文件,将这个对象放入到模块文件中,并且通过conments规范暴露这个对象, 当其他同学需要的时候,可以通过cmd规范语法,利用require来引入这个模块文件并且利用一个参数来接收, 下面上代码:试例: //工具模块 // 配置工具模块 define(function(require,exports,module) { var observer = (function() { var ob = {}; return { on:function(type,fn) { ob[type] = fn; }, trigger:function(type,val) { obtype; } } }) (); module.exports = observer; })
//a同学接收模块 // a同学模块 define(function(require,exports,module) { var observer = require("modules/tools"); // 这里接收了哦! var num = document.getElementById("num"); observer.on("add",function() { num.innerHTML++; }); observer.on("remove",function() { num.innerHTML--; }) })
接下来就是实现功能了 ,上代码
//b同学所写模块 , define(function (require, exports, module) { var observer = require("modules/tools"); var ul = document.getElementById("list"); // observer.on("add2", function (val) { var li = document.createElement("li");
var textNode = document.createTextNode(val);
var span = document.createElement("span");
span.innerHTML = "×";
// 监听点后 下树 数量减少 此模块不参与其他模块工作 引入功能即可
span.onclick = function () {
observer.trigger("remove");
ul.removeChild(li);
}
if (val) {
li.appendChild(textNode);
ul.appendChild(li);
}
})
复制代码
})
综上代码描述可以发现,b同学主要负责的就是li区域的内容编写,例如li追加到ul身上,span追加到li身上, 用户提交的信息追加到Li身上,这里画重点了,如果信息,追加到li身上那么,那么这个信息怎么获取呢,? 其实这个信息是c同学负责获取的,但是获取信息的同时需要做什么呢?可以看一下代码! // 第三模块发布模块 define(function (require, exports, module) { var observer = require("modules/tools"); var btn = document.getElementById("btn"); var words = document.getElementById("words"); btn.onclick = function () { var val = words.value; // 当对话框中有值传递的时候那么执行一项操作 if (val) {
observer.trigger("add");
}
}
observer.trigger("add2", val);
复制代码
})
可以在代码中看到 调用使用观察者对象(Observer)中的 trigger属性时(也就是调用函数) 第二个参数传入了用户输入的信息, 那么这个时候哪里来接收这个参数呢?
答案就是上面的这句话了: 需要传入参数时: 就是需要传到方法定义的上面去, 方法的定义是哪里? 当然是模块文件中定义该方法的地方啦 所以再看一下这里的代码
//观察者对象调用这个trigger方法时,其实就是在执行On的方法,
trigger:function(type,val) {
ob[type](val); //==>ob.type(val) 看这里理解
}
}
复制代码
那么这里既然传递参数了,对应的定义模块就应该有参数来接受 这是//b同学的定义模块 observer.on("add2", function (val) { var li = document.createElement("li");
var textNode = document.createTextNode(val);
var span = document.createElement("span");
span.innerHTML = "×";
// 监听点后 下树 数量减少 此模块不参与其他模块工作 引入功能即可
span.onclick = function () {
observer.trigger("remove");
ul.removeChild(li);
}
if (val) {
li.appendChild(textNode);
ul.appendChild(li);
}
})
复制代码
上述方法:所表述的还只是一个初级监听者模式,那么还有什么更高级的方式吗?
可以思考一下,目标这个模式有哪些缺点: 聪明的你是否已经想到: 首先,这类模式类似于dom0级的绑定方式,当需要绑定多个同名的函数的时候就会出现后面覆盖前面的这种情况,并不利于实际开发 并且,需要定义多个事件名称实现不同功能,那么是否有何种方式来解决这类问题呢 ,那么就是高级观察者模式登场的时候了。
先看一下代码: // 升级观察者 var Observer = (function() { // 定义真正的观察者 var ob = { // aaa: [function() {console.log("ccc")}]; }
// 定义接口
return {
on: function(type, fn) {
// 判断当前事件属性是否存在,直接push即可
if (ob[type]) {
ob[type].push(fn);
} else {
ob[type] = [fn];
// ob.type = [f]
}
},
// trigger用于触发观察者对象中的事件
trigger: function(type, val) {
for (var i = 0; i < ob[type].length; i++) {
ob[type] =>进入到了ob中 ob[i]=>读取对应所以位的函数 =>ob[i]()=>执行这个函数
ob[type][i](val);
}
},
check: function() {
console.log(ob);
}
}
})()
// 向外暴露功能
module.exports = Observer;
复制代码
这个工具模块依旧是俩个功能一个是on生成功能,一个是trigger调用功能,(最后一个是检测ob中的结构的)不纳入其中,
那么和上面的不同点是什么呢?
on:首先利用If,判断传入的事件名称是否在事件对象中存在,如果不存在那么就是直接将这个事件放入到一个数组中, 如果存在那么就利用数组的Push方法直接将该方法添加到数组, 这样子就达成了一个事件名称储存多个函数了, 而初级的并不可以(地址直接进行了覆盖)
trigger:那么如何进行调用呢? 调用的方式也很简单就是循环遍历 ob观察者对象中的 对应事件数组,对数组的每项都进行调用
这样子最终的好处就是该微博信息功能板块 可以只利用一个事件名称进行多个方法的制作,调用时直接利用这个事件名称进行调用就可以了。
最终看一下代码:注意调用时所传递的事件名称 以及 定义功能时所传递的事件名称 //a同学 define(function(require, exports, module) { // console.log("第一个模块"); // 引入工具模块 var Observer = require("modules/tools"); // 获取元素 var num = document.getElementById("num");
// console.log(Observer);
// 监听消息添加事件
Observer.on("add", function() {
num.innerHTML++;
})
// 监听消息减少事件
Observer.on("remove", function() {
num.innerHTML--;
})
复制代码
})
//b同学 // 定义模块 define(function(require, exports, module) { // console.log("第二个模块"); // 引入工具模块 var Observer = require("modules/tools"); // console.log(Observer)
// 获取元素
var ul = document.getElementById("list");
// 监听消息添加事件
Observer.on("add", function(val) {
// 创建一个li元素
var li = document.createElement("li");
// 创建文本节点
var textNode = document.createTextNode(val);
// 创建span元素
var span = document.createElement("span");
// 设置内部文本
span.innerHTML = "×";
// 注册点击事件
span.onclick = function() {
ul.removeChild(this.parentNode);
// 这里要告诉第一个模块,信息数量要减少
// 触发事件
Observer.trigger("remove");
}
// 追加span元素
li.appendChild(span);
// 追加文本节点
li.appendChild(textNode);
// 将li追加到ul中
ul.appendChild(li);
})
复制代码
})
c同学 // 定义模块 define(function(require, exports, module) { // console.log("第三个模块"); // 引入工具模块 var Observer = require("modules/tools"); // 获取元素 var words = document.getElementById("words"); // 提交按钮 var btn = document.getElementById("btn");
// 给btn注册点击事件
btn.onclick = function() {
// 获取用户输入的信息
var val = words.value;
// console.log(val);
if (val) {
// 触发事件
Observer.trigger("add", val);
}
}
复制代码
})
实现off方法: off方法就是传递了事件名称以及事件函数,那么就循环遍历包含相同事件名称的数组, 如果找到对应的事件函数,那么就将这个事件函数在数组中移除, 如果传递一个参数 事件名称 的时候,就直接通过[]这种简单暴力的方式来置空数组;
这里有一个知识点就是函数重载,arguments.length==1 || arguments.length ==2;
因为要知道传递的参数是有序的,fn一定是第二个参数,所以说他有点像函数重载,但是书写方式简单,还是利用这种方式吧。 下面是代码 off:function(type,fn) { if(fn) { for(var i = 0; i < ob[type].length;i++) { if(ob[type][i] == fn) { ob[type].splice(i,1); } } }else if(type) { ob[type] = []; } else { ob = {}; } }
实现Once方法: //once方法:主要为了实现通过该方法绑定的事件,只能够进行调用一次,再次调用无法调用 , 其实思想很简单 就是利用一个中间函数,中间函数绑定并且执行传递进来的参数函数,执行之后再利用off方法删除此函数,在利用on方法来调用那个中间函数, 那么在次调用的时候,已经被Off方法把这个中间函数移除掉了。再怎么调用也都不会找到这个方法了
代码: once:function(type,fn) { function aaa () { fn(); off(type,aaa); } observer.on(type,aaa); }
最后的总结: 观察者模式在多人开发中非常实用: 通过定义一个工具模块, 工具模块:利用一个立即执行函数以及闭包的缓存机制为对象添加不同的功能,
添加功能On:利用数组对同类事件名称的事件函数进行储存,
调用功能trigger:利用循环遍历遍历事件名称当中的所有事件并且执行。
删除功能off:利用传递进来的参数判断所需要删除是一项事件函数,还是整个数组,
如果有函数那么就是删除仅仅删除数组中的这一个函数而已,怎么查找到这个函数就是利用For循环遍历这个数组中的每一项,并且进行判断 ,当满足条件的时候,那么就通过数组方法来移除, 反之是type一个参数的时候就置空数组
仅限被调用一次的功能Once:利用中间函数执行传递的函数,在通过off方法移除这个中间函数,最后通过On方法来绑定这个中间函数,
其他人引入这个工具模块, 利用工具模块所定义好的方法,来使用。
在一个模块具体功能区中,往往只利用一个事件名称就可以,因为On方法会将同类事件名称的函数都储存在数组当中。 当需要执行的时候回依次遍历数组去执行