事件模型
事件模型
在我们前端领域,事件模型使用最多与最常见的就是 DOM 事件模型。听说最多的就是“事件监听”, “事件代理”这名词。
它能避免对多个 DOM 节点添加相同的事件、避免通过 JS 添加的 DOM 节点也可触发相关事件等等。
DOM事件模型(冒泡、捕获)
在对 DOM 操作时,可给对应的 DOM 节点添加 Click 等事件。在 DOM 事件模型中,分为“冒泡”与“捕获”两个方式。默认为“冒泡”。
以下都以“冒泡”为主要操作方向。“捕获”不做更多解释。
“捕获”与“冒泡”的生命周期
在该 DOM 树结构中,以click来解释为。当我们对最底层的 div 发生一个 click 事件时,“捕获”流程开始,会依次由 window 顶层节点开始依次往下寻找直到触发事件的 div 节点,"捕获"的生命周期结束。接着“冒泡”的生命周期开始。会根据 DOM 树往上传递,一直到 window “冒泡”生命周期结束。
这两种形式在代码层面如下
//click 事件回调
var clickFn = function(e){
// e 为事件对象,内部拥有触发该事件时,鼠标相对于屏幕的X、Y值,触发事件的 DOM 对象。
// 更详细的信息可通过打印该对象进行阅读。
console.log(e);
// 针对某节点名称进行控制
if(e.target.tagName.toLocaleLowerCase() === 'div'){
console.log('点击的是 DIV 节点')
}
// 针对 class 名称进行控制
if(e.target.className.indexOf('className') >= 0) {
console.log('点击的是 className');
}
};
document
.getElementsByTagName('body')[0]
.addEventListener('click', clickFn, false) //启用冒泡
document
.getElementsByTagName('body')[0]
.addEventListener('click', clickFn, true) //启用捕获
JQuery code
var clickFn = function(e){
// 可将 e 对象数据打印出来,查看具体信息
console.log(e);
}
$('body').on('click', 'JQuery select', clickFn);
鉴于冒泡的特性,我们可在最外层的 DOM 节点添加相关的事件,
因此我们可以基于以上理论实现一个比较常规的样式操作方法。具体实现效果操作如图
//style code
ul {
display: flex;
height: 50px;
}
ul li {
flex: 1;
height: inherit;
}
ul li.active {
color: #ff6900;
border-bottom: 1px solid #ff6900;
box-sizing: border-box;
text-align: center;
}
// HTML code
<ul>
<li>
<span>24小时榜单</span>
</li>
<li>
<span>7天榜单</span>
</li>
<li>
<span>30天榜单</span>
</li>
</ul>
//js code
$('ul').on('click', 'li', function(e){
console.log(e.target); //span node
console.log(e.currentTarget); //li node
$(e.currentTarget)
.addClass('active')
.siblings()
.removeClass('active');
});
注意,在使用JQuery进行冒泡监听时,需要使用 e.currentTarget 该对象。e.target 为触发事件的 DOM 对象。e.currentTarget 为 on() 第二个参数所添加进入的 DOM 对象。
on(eve,[sel],[data], fn) And trigger(type,[data])
对 on(eve, [sel], [data], fn) 方法的解释。官方
在选择元素上绑定一个或多个事件的事件处理函数。
对 trigger(type, [data]) 方法解释。
在每一个匹配的元素上触发某类事件。
// 对 body 添加一个 click 事件
var $body = $('body').on('click', function(){console.log('this body')});
//手动触发 body 的 click 事件
$body.trigger('click'); //控制台会看到 'this body';
// 声明一个自定义 body 事件
$body.on('sayBodyName', function(){console.log('say this body')});
//触发自定义事件
$body.trigger('sayBodyName'); //控制台会看到 'say this body';
在WEB前端领域中,我们大部分的工作都是基于用户的某些操作而响应某些事件。处于响应式、被动的场景。因而经典的设计模式 MVC 在前端领域中仅能完成通过 AJAX 获取到数据后,经过一系列的处理,最终展示到浏览器终端上。因此塔的生命周期为获取数据(M) -> 拼接数据(C) -> 展示数据(V),仅此而已。但是在实际操作中,我们还得响应用户的某些动作,而做出不同的操作,因此 MVC 在前端领域中 view 层变得很繁重,且格格不入。因而还衍生了一系列新颖的设计模式MVP、MVVM等等。基于前面我们所了解JQuery特性,我们可模拟 MVVM 模式中的 VM 部分。
现在我们使用上面所使用的 HTML 结构进重写。
//style code
ul {
display: flex;
height: 50px;
}
ul li {
flex: 1;
height: inherit;
}
ul li.active {
color: #ff6900;
border-bottom: 1px solid #ff6900;
box-sizing: border-box;
text-align: center;
}
// HTML code
<ul>
<li>
<span>24小时榜单</span>
</li>
<li>
<span>7天榜单</span>
</li>
<li>
<span>30天榜单</span>
</li>
</ul>
// js code
var viewAction = {
navClickFn: function(e){
var $ele = $(e.currentTarget);
var pushData = {text: $ele.text(), value: $ele.attr('data-value')};
// 将 pushData 提交至新的 AJAX 请求;
$ele
.addClass('active')
.siblings()
.removeClass('active');
},
getNavList: function(){
// ajax 获取数据 $.get('');
var data = {list: [
{text: '24小时榜单', value: 1},
{text: '7天榜单', value: 2},
{text: '30天榜单', value: 3}
]};
navList.trigger('render', data)
},
renderList: function(e, data){
var ul = $('<ul/>', {class: 'nav-list'});
var li = $('<li/>', {class: 'nav-item'});
var span = $('<span/>, {class: 'nav-item-text'});
$.each(data.list, function(k, v){
ul.append(
li.clone().append(
span.clone().text(v.text).attr('data-value', v.value)
)
);
});
navList.html(ul);
},
};
navList = ('nav-list')
.on('click', '.nav-item', viewAction.navClickFn)
.on('getNavList', viewAction.getNavList)
.on('renderList', viewAction.renderList)
.on('render', function(e, data){
navList.trigger('renderList', data);
});
以上就简单封装了一个 VM 模型。但是,通过 AJAX 获取到的数据与响应事件后的数据我们都是通过 trigger() 内部进的交互。比较难进行跟踪,所以,我们要实现一个保存数据的对象,并且可对该对象进一系列的控制。我们就将这个对象当做我们的 MVVM 中的 M 模型
// 现在我们将上面所有的数据摘取出来
// 声明一个 viewModule 对象;
var viewModule = {};
// 将 getNavList 修改为
viewAction.getNavList = function(){
// ajax 获取数据 $.get('');
viewModule.navList = {list: [
{text: '24小时', value: 1},
{text: '一周', value: 2},
{text: '一月', value: 3}
]};
navList.trigger('render')
}
// renderList 修改为
viewAction.renderList: function(e){
var ul = $('<ul/>', {class: 'nav-list'});
var li = $('<li/>', {class: 'nav-item'});
var span = $('<span/>, {class: 'nav-item-text'});
$.each(viewModule.navList.list, function(k, v){
ul.append(
li.clone().append(
span.clone().text(v.text).attr('data-value', v.value)
)
);
});
navList.html(ul);
}
到这里,我们将使用 trigger() 进行通讯的数据摘取出来,通过外部的 viewModule 作为数据层,对需要使用数据的位置进行服务。但是这样做不太友好和安全,因为 viewModule 该对象没有做任何的保护,任何位置,任何地方都可以对它进行修改,且有时候我们还不知道我们已经修改了它。因此,我们需要对该对象进行一些封装。通过 闭包 实现对象私有化最快的方式。
var viewModule = (function(){
var obj = {
name: 'YangWs'
};
return {
set: function(name){
return obj[name];
},
get: function(name, value){
obj[name] = value;
reurn obj[name];
}
};
})();
这样,外部就无法直接操作 obj 对象,要对其进操作,只能通过 get(), set() 这两个方法。
但是,到目前为止,该模型只能进行保存值、取值的动作,而外部无法监到听内部的值是否有改变了。
对此,我们再对该 对象 添加一系列的方法扩展该对象的功能。接下来我们会使用到 订阅 与 发布的模型。
订阅(Sub)
发布(Pub)
他们的理论都很简单,和 JQuery 的 on 方法很像,对 DOM 对象添加一个 on/订阅 事件,当触发 on/订阅 事件时就执行通过 on/订阅 的函数,当触发时就是 trigger/发布 的过程。
更确切的来说,JQuery 通过 on 是实现 观察者模式。
接下来我们简单实现 订阅/发布 的模型
var subAndPub = function(){
var eventList = {};
return {
on: function (name, cb) {
if (Object.prototypt.toString.call(cb).indexOf('Function') >= 0) {
eventList[name] = eventList[name] ? eventList[name] : [];
eventList[name].push({
eventName: $.trim(name),
cb : cb
});
}
return this;
},
trigger: function (name) {
if (Object.prototype.toString.call(name).indexOf('String') >= 0) {
eventList[name] && $.each(eventList[name], function (k, v) {
//将触发事件方法插入eventLoop底部,等待each遍历完成后再执行函数
setTimeout(function () {
v.cb();
}, 0);
})
}
return this;
}
}
};
subAndPub
.on('say', function(){console.log('this one say')})
.on('say', function(){console.log('this two say')})
.trigger('say');
这样,我们就实现了通过 on 与 trigger 进行订阅与发布的简单模型。
最终我们可以将以上的 VM、M 稍加修改后,结合起来就可家简单实现这几年前端各个框架拉新所使用的名词双向绑定