事件
JavaScript和HTML的交互是通过事件实现的。JavaScript采用异步事件驱动编程模型,当文档、浏览器、元素或与之相关对象发生特定事情时,浏览器会产生事件。如果JavaScript关注特定类型事件,那么它可以注册当这类事件发生时要调用的句柄
- 事件是某个行为或者触发,比如点击、鼠标移动
- 当用户点击鼠标时
- 当网页已加载时
- 当图像已加载时
- 当鼠标移动到元素上时
- 当用户触发按键时...
事件无处不在,在javascript中,我们如何去观察事件呢?
事件流
事件流描述的是从页面中接收事件的顺序,比如有两个嵌套的div,点击了内层的div,这时候是内层的div先触发click事件还是外层先触发?
说明: 例如这里的button,我点击的时候,是先触发'wrap',还是先触发button
目前主要有三种模型
- IE的事件冒泡:事件开始时由最具体的元素接收,然后逐级向上传播到较为不具体的元素
事件捕获:不太具体的节点更早接收事件,而最具体的元素最后接收事件,和事件冒泡相反
DOM事件流:DOM2级事件规定事件流包括三个阶段,事件捕获阶段,处于目标阶段(到达目标的那个事件),事件冒泡阶段,首先发生的是事件捕获,为截取事件提供机会,然后是实际目标接收事件,最后是冒泡阶段
这种分歧在日常生活中也很常见,举个例子,某个地方出了抢劫事件,我们有多种处理方式
- 村里先发现,报告给乡里,乡里报告到县城,县城报告给市里。。。。
- 市里先知道这事儿,然后交代给县城怎么处理,县城交给到乡里处理,乡里交给村里处理
Opera、Firefox、Chrome、Safari都支持DOM事件流,IE不支持事件流,只支持事件冒泡
如有以下html
<!DOCTYPE html >
<html>
<head>
<meta http-equiv="Content-type" content="text/html; charset=utf-8" />
<title>Test Page</title>
</head>
<body>
<div>·Click Here</div>
</body>
</html>
点击div区域
事件冒泡模型 | 事件捕获模型 | DOM事件流 |
事件处理程序
我们也称之为事件侦听器(listener),事件就是用户或浏览器自身执行的某种动作。比如click、load、mouseover等,都是事件类型(俗称事件名称),而响应某个事件的方法就叫做事件处理程序或者事件监听器
也就是我们需要提前定义好某些事件发生了该怎么处理,这个过程叫做绑定事件处理程序,了解了这些,我们看看如何给元素添加事件处理程序
HTML内联方式
元素支持的每个事件都可以使用一个相应事件处理程序同名的HTML属性指定。这个属性的值应该是可以执行的JavaScript代码,我们可以为一个button添加click
事件处理程序(这种方式在早期使用,因为还没有样式与操作分离的概念)
<input type="button" value="Click Here" onclick="alert('Clicked!');" />
在HTML事件处理程序中可以包含要执行的具体动作,也可以调用在页面其它地方定义的脚本,刚才的例子可以写成这样
<input type="button" value="Click Here" onclick="showMessage();" />
在HTML中指定事件处理程序书写很方便,但是有两个缺点。
存在加载顺序问题,如果事件处理程序在html代码之后加载,用户可能在事件处理程序还未加载完成时就点击按钮之类的触发事件,存在时间差问题
这样书写html代码和JavaScript代码紧密耦合,维护不方便
JavaScript指定事件处理程序
通过JavaScript指定事件处理程序就是把一个方法赋值给一个元素的事件处理程序属性。
每个元素都有自己的事件处理程序属性,这些属性名称通常为小写,如onclick
等,将这些属性的值设置为一个函数,就可以指定事件处理程序,如下
<input id="btnClick" type="button" value="Click Here" />
<script type="text/javascript">
var btnClick = document.getElementById('btnClick');
btnClick.onclick = function showMessage() {
alert(this.id);
};
</script>
这样处理,事件处理程序被认为是元素的方法,事件处理程序在元素的作用域下运行,this就是当前元素,所以点击button结果是:btnClick
这样还有一个好处,我们可以删除事件处理程序,只需把元素的onclick属性赋为null即可
DOM2事件处理程序
DOM2级事件定义了两个方法用于处理指定和删除事件处理程序的操作:
- addEventListener
- removeEventListener
所有的DOM节点都包含这两个方法,并且它们都接受三个参数:
- 事件类型
- 事件处理方法
- 布尔参数,如果是true表示在捕获阶段调用事件处理程序,如果是false,则是在事件冒泡阶段处理
刚才的例子我们可以这样写
<input id="btnClick" type="button" value="Click Here" />
<script type="text/javascript">
var btnClick = document.getElementById('btnClick');
btnClick.addEventListener('click', function() {
alert(this.id);
}, false);
</script>
上面代码为button添加了click事件的处理程序,在冒泡阶段触发,与上一种方法一样,这个程序也是在元素的作用域下运行,不过有一个好处,我们可以为click事件添加多个处理程序
<input id="btnClick" type="button" value="Click Here" />
<script type="text/javascript">
var btnClick = document.getElementById('btnClick');
btnClick.addEventListener('click', function() {
alert(this.id);
}, false);
btnClick.addEventListener('click', function() {
alert('Hello!');
}, false);
</script>
这样两个事件处理程序会在用户点击button后按照添加顺序依次执行。
通过addEventListener添加的事件处理程序只能通过removeEventListener移除,移除时参数与添加的时候相同,这就意味着刚才我们添加的匿名函数无法移除,因为匿名函数虽然方法体一样,但是句柄却不相同,所以当我们有移除事件处理程序的时候可以这样写
<input id="btnClick" type="button" value="Click Here" />
<script type="text/javascript">
var btnClick = document.getElementById('btnClick');
var handler=function() {
alert(this.id);
}
btnClick.addEventListener('click', handler, false);
btnClick.removeEventListener('click', handler, false);
</script>
事件冒泡
<style>
.container,
.box,
.target{
border: 1px solid;
padding: 10px;
}
</style>
<button id="btn">click</button>
<div class="container">
container
<div class="box">
box
<div class="target">target</div>
</div>
</div>
<script>
function $(selector){
return document.querySelector(selector)
}
var btn = $('#btn')
btn.onclick = function (e){
console.log(e)
}
btn.addEventListener('click', function(evt){
console.log(this)
console.log(btn)
console.log(evt.target)
})
$('.container').addEventListener('click', function(e){
console.log('contianer click.. in 捕获阶段')
}, true)
$('.box').addEventListener('click', function(e){
//e.stopPropagation()
console.log('box click.. in 捕获阶段')
}, true)
$('.target').addEventListener('click', function(e){
console.log('target click.. in 捕获阶段')
}, true)
$('.container').addEventListener('click', function(e){
console.log('contianer click.. in 冒泡阶段')
}, false)
$('.box').addEventListener('click', function(e){
//e.stopPropagation()
console.log('box click.. in 冒泡阶段')
}, false)
$('.target').addEventListener('click', function(e){
console.log('target click.. in 冒泡阶段')
}, false)
</script>
通过this得知监听的元素
阻止默认事件
<a href="http://baid.com">baidu</a>
<script>
document.querySelector('a').onclick= function(e){
e.preventDefault()
console.log(this.href)
if(/baidu.com/.test(this.href)){
location.href = this.href
}
}
</script>
<form action="/login">
<input type="text" name="username">
<input type="submit">
</form>
<script>
document.querySelector('form').addEventListener('submit', function(evt){
evt.preventDefault()
if(document.querySelector('input[name=username]').value === 'jirengu'){
this.submit()
}
})
</script>
事件代理
<div class="container">
<div class="box">box1</div>
<div class="box">box2</div>
<div class="box">box3</div>
</div>
<button id="add">add</button>
<script>
function $(selector){
return document.querySelector(selector)
}
function $$(selector){
return document.querySelectorAll(selector)
}
// $$('.box').forEach(function(node){
// node.onclick = function(){
// console.log(this.innerText)
// }
// })
$('.container').onclick = function(e){
console.log(this)
console.log(e.target)
if(e.target.classList.contains('box')){
console.log(e.target.innerText)
}
}
var i = 4
$('#add').onclick = function(){
var box = document.createElement('div')
box.classList.add('box')
box.innerText = 'box' + (i++)
$('.container').appendChild(box)
}
事件代理实例
具体需求:
1.点击列表时,在div中展示点击的栏目的内容
2.输入框输入信息后,点击添加,新生成一个li添加到列表中
3.点击新添加的li,也能在下方div中展示其中的内容
1.点击li,展示内容
2.添加li
但此时点击新增的li,并不会展示在下面的div中。因为代码自上而下执行,前面我们通过forEach对已有的元素进行了事件绑定,但新增的元素并没有进行事件绑定,所以点击新增元素无法展示内容。
虽然实现了需求,但这里有不合理的地方,有时我们需要从数据库获取许多内容,然后将这些内容拼接成html,作为新栏目放置在页面上,这时,对于这些新增的元素,如果要实现一些功能,但上面的写法就要一一对元素进行事件绑定,这样很麻烦。这时,我们可以使用事件代理实现功能。
3.可显示新增li内容
事件代理,将事件绑定到父元素后,代码量少了许多
bug
这样还是有一个问题,当li较小时,我们点击到了ul,但这里还是会打印出所有的内容,这是需要对点击的地方做一个判断
IE兼容性
说明:对于attachEvent,在它的回调函数中想要知道监听的事件,可通过window.event获取
跨浏览器的事件处理程序
前面内容我们可以看到,在不同的浏览器下,添加和移除事件处理程序方式不相同,要想写出跨浏览器的事件处理程序,首先我们要了解不同的浏览器下处理事件处理程序的区别
在添加事件处理程序事addEventListener和attachEvent主要有几个区别
参数个数不相同,这个最直观,addEventListener有三个参数,attachEvent只有两个,attachEvent添加的事件处理程序只能发生在冒泡阶段,addEventListener第三个参数可以决定添加的事件处理程序是在捕获阶段还是冒泡阶段处理(我们一般为了浏览器兼容性都设置为冒泡阶段)
第一个参数意义不同,addEventListener第一个参数是事件类型(比如click,load),而attachEvent第一个参数指明的是事件处理函数名称(onclick,onload)
-
事件处理程序的作用域不相同,addEventListener的作用域是元素本身,this是指的触发元素,而attachEvent事件处理程序会在全局变量内运行,this是window,所以刚才例子才会返回undefined,而不是元素id
说明:也就是说,当我们在IE中写的时候,这里的this是window,而不是box
- 为一个事件添加多个事件处理程序时,执行顺序不同,addEventListener添加会按照添加顺序执行,而attachEvent添加多个事件处理程序时顺序无规律(添加的方法少的时候大多是按添加顺序的反顺序执行的,但是添加的多了就无规律了),所以添加多个的时候,不依赖执行顺序的还好,若是依赖于函数执行顺序,最好自己处理,不要指望浏览器
了解了这四点区别后我们可以尝试写一个浏览器兼容性比较好的添加事件处理程序方法(也就是说,我们要对事件进行封装)
//将两种写法融合到一个函数里面,也就是实现封装
function addEvent(node, type, handler) {
if (!node) return false;
if (node.addEventListener) {
node.addEventListener(type, handler, false);
return true;
}
else if (node.attachEvent) {
node.attachEvent('on' + type, handler, ); //在IE中,事件要加上on
return true;
}
return false;
}
这样,首先我们解决了第一个问题参数个数不同,现在三个参数,采用事件冒泡阶段触发
第二个问题也得以解决,如果是IE,我们给type添加上on
第四个问题目前还没有解决方案,需要用户自己注意,一般情况下,大家也不会添加很多事件处理程序
试试这个方法感觉很不错,但是我们没有解决第三个问题,由于处理程序作用域不同,如果handler内有this之类操作,那么就会出错。在IE下,实际上大多数函数都会有this操作
function addEvent(node, type, handler) {
if (!node) return false;
if (node.addEventListener) { //此处实际是在进行能力检测,也就是说,我不看
//你到底是哪个浏览器,能不能用这个功能,我直接看你是否具有这项能力
node.addEventListener(type, handler, false);
return true;
}
else if (node.attachEvent) {
node.attachEvent('on' + type, function() { handler.apply(node); });
return true;
}
return false;
}
这样处理就可以解决this的问题了,但是新的问题又来了,我们这样等于添加了一个匿名的事件处理程序,无法用detachEvent取消事件处理程序,有很多解决方案,我们可以借鉴大师的处理方式,jQuery创始人John Resig是这样做的
function addEvent(node, type, handler) {
if (!node) return false;
if (node.addEventListener) {
node.addEventListener(type, handler, false);
return true;
}
else if (node.attachEvent) {
node['e' + type + handler] = handler;
node[type + handler] = function() {
node['e' + type + handler](window.event);
};
node.attachEvent('on' + type, node[type + handler]);
return true;
}
return false;
}
说明:这里相对上面添加的代码,功能上就是为了实现在console.log(this)的时候,打印出来的还是响应时间的节点
在取消事件处理程序的时候
function removeEvent(node, type, handler) {
if (!node) return false;
if (node.removeEventListener) {
node.removeEventListener(type, handler, false);
return true;
}
else if (node.detachEvent) {
node.detachEvent('on' + type, node[type + handler]);
node[type + handler] = null;
}
return false;
}
John Resig很巧妙地利用了闭包,看起来很不错。
事件对象
在触发DOM上的某个事件的时候会产生一个事件对象event,这个对象包含着所有与事件有关的信息,包括产生事件的元素、事件类型等相关信息。所有浏览器都支持event对象,但支持方式不同。
DOM中的事件对象
兼容DOM的浏览器会产生一个event对象传入事件处理程序中。应用一下刚才我们写的addEvent方法
var btnClick = document.getElementById('btnClick');
addEvent(btnClick, 'click', handler);
点击button的时候我们可以看到弹出内容是click的弹窗
event对象包含与创建它的特定事件有关的属性和方法,触发事件的类型不同,可用的属性和方法也不同,但是所有事件都会包含
属性/方法 | 类型 | 读/写 | 说明 |
---|---|---|---|
bubbles | Boolean | 只读 | 事件是否冒泡 |
cancelable | Boolean | 只读 | 是否可以取消事件的默认行为 |
currentTarget | Element | 只读 | 事件处理程序当前处理元素 |
detail | Integer | 只读 | 与事件相关细节信息 |
eventPhase | Integer | 只读 | 事件处理程序阶段:1 捕获阶段,2 处于目标阶段,3 冒泡阶段 |
preventDefault() | Function | 只读 | 取消事件默认行为 |
stopPropagation() | Function | 只读 | 取消事件进一步捕获或冒泡 |
target | Element | 只读 | 事件的目标元素 |
type | String | 只读 | 被触发的事件类型 |
view | AbstractView | 只读 | 与事件关联的抽象视图,等同于发生事件的window对象 |
在事件处理程序内部,this
始终等同于currentTarget
,而target是事件的实际目标。
重要概念,取消事件冒泡,取消默认事件
说明:这样做有什么意义呢?当我们做一个模态框的效果时,点击模态框本身,框不消失,点击页面其他地方,框消失,需要用到这个方法。如果我们不对这个click的时间进行阻止,那么我们点击任何位置都会将这个事件传播到body上,点击页面和点击模态框就没了区别。
要阻止事件的默认行为,可以使用preventDefault()
方法,前提是cancelable值为true,比如我们可以阻止链接导航这一默认行为
document.querySelector('#btn').onclick = function (e) {
e.preventDefault();
}
stopPropagation()
方法可以停止事件在DOM层次的传播,即取消进一步的事件捕获或冒泡。我们可以在button的事件处理程序中调用stopPropagation()从而避免注册在body上的事件发生
var handler = function (e) {
alert(e.type);
e.stopPropagation();
}
addEvent(document.body, 'click', function () { alert('Clicked body')});
var btnClick = document.getElementById('btnClick');
addEvent(btnClick, 'click', handler);
若是注释掉e.stopPropagation(); 在点击button的时候,由于事件冒泡,body的click事件也会触发,但是调用这句后,事件会停止传播
IE中的事件对象
访问IE中的event对象有几种不同的方式,取决于指定事件处理程序的方法。直接为DOM元素添加事件处理程序时,event对象作为window对象的一个属性存在。(在DOM2中,回调函数传入的参数直接就是监听的事件,直接就能拿来用)
var handler = function () {
var e = window.event;
alert(e.type);
}
var btnClick = document.getElementById('btnClick');
btnClick.onclick = handler;
我们通过window.event取得了event对象,并检测到了其类型,可是如果事件处理程序是通过attachEvent添加的,那么就会有一个event对象被传入事件处理程序中
var handler = function (e) {
alert(e.type);
}
var btnClick = document.getElementById('btnClick');
attachEvent(btnClick, handler);
当然这时候也可以通过window对象访问event,方便起见,我们一般会传入event对象,IE中所有的事件都包含以下属性方法
属性/方法 | 类型 | 读/写 | 说明 |
---|---|---|---|
cancelBubble | Boolean | 读/写 | 默认为false,设置为true后可以取消事件冒泡 |
returnValue | Boolean | 读/写 | 默认为true,设为false可以取消事件默认行为 |
srcElement | Element | 只读 | 事件的目标元素 |
type | String | 只读 | 被触发的事件类型 |
封装实现选择器
通过封装简化写法
跨浏览器的事件对象
虽然DOM和IE的event对象不同,但基于它们的相似性,我们还是可以写出跨浏览器的事件对象方案
function getEvent(e) {
return e || window.event;
}
function getTarget(e) {
return e.target || e.scrElement;
}
function preventDefault(e) {
if (e.preventDefault)
e.preventDefault();
else
e.returnValue = false;
}
function stopPropagation(e) {
if (e.stopPropagation)
e.stopPropagation();
else
e.cancelBubble = true;
}
常用HTML事件
<button id="btn">点我</button>
<button id="btn1">点我1</button>
<div class="ct" style="font-size: 20px">
<div class="box">hello</div>
</div>
<div class="ct1">
<div class="box1"></div>
</div>
<input id="input-name" type="text">
<form id="form" action="/upload">
<input id="username" name="username" type="text">
<p class="msg"></p>
<input id="btn-submit" type="submit" value="注册">
</form>
<img src="https://jirengu.com/data/upload/2017/0118/17/587f39fba695a.png" alt="">
<script>
function $(selector){
return document.querySelector(selector);
}
$('#btn').addEventListener('click', function(){
console.log('click')
console.log(this)
})
$('#btn1').addEventListener('dblclick', function(){
console.log('dblclick')
console.log(this)
})
$('.ct').addEventListener('mouseover', function(){
console.log('mouseover')
console.log(this)
// this.style.borderColor = 'blue'
this.classList.add('hover')
})
$('.ct').addEventListener('mouseout', function(){
console.log('mouseout...')
// this.style.borderColor = 'red'
this.classList.remove('hover')
})
$('.ct1').addEventListener('mouseenter', function(){
console.log('mouseenter...')
//this.style.borderColor = 'blue'
this.classList.add('hover')
})
$('.ct1').addEventListener('mouseleave', function(){
console.log('mouseleave...')
//this.style.borderColor = 'blue'
this.classList.remove('hover')
})
$('#input-name').addEventListener('focus', function(){
console.log('focus...')
console.log(this.value)
})
$('#input-name').addEventListener('blur', function(){
console.log('blur...')
console.log(this.value)
})
$('#input-name').addEventListener('keyup', function(e){
console.log('keyup...')
console.log(this.value)
console.log(e)
this.value = this.value.toUpperCase()
})
$('#input-name').addEventListener('change', function(e){
console.log('change...')
console.log(this.value)
console.log(e)
this.value = this.value.toUpperCase()
})
$('#form').addEventListener('submit', function(e){
e.preventDefault();
if(/^\w{6,12}$/.test($('#username').value)){
$('#form').submit();
}else{
$('#form .msg').innerText = '出错了'
$('#form .msg').style.display = 'block'
console.log(' no submit...');
}
})
window.addEventListener('scroll', function(e){
console.log('scroll..')
})
window.addEventListener('resize', function(e){
console.log('resize..')
})
//页面所有资源加载完成
window.onload = function(){
console.log('window loaded')
}
//DOM 结构解析完成
document.addEventListener('DOMContentLoaded', function(){
console.log('DOMContentLoaded ')
})
console.log($('img').width) //0
$('img').onload = function(){
console.log(this.width) //此时才能得到图片的真实大小
}
</script>
<style>
body{
color: blue;
}
.ct,.ct1{
width: 100px;
height: 100px;
border: 1px solid red;
background-color: yellow;
margin: 20px;
}
.box,.box1{
width: 50px;
height: 50px;
background-color: blue;
}
.ct.hover, .ct1.hover{
border-color: blue;
background-color: pink;
}
.box3{
list-style: none;
background: yellow;
margin: 0;
padding: 0;
}
.box3>li{
background: pink;
margin: 5px;
padding: 10px;
}
.box3>li.hover{
background-color: blue;
}
.msg{
display: none;
}
</style>
鼠标事件
onmousedown, onmouseup, onclick, ondbclick, onmousewheel, onmousemove, onmouseover, onmouseout
相关细节
-
对象上的style属性
-
获取元素的真实样式
说明:style属性只能获取到html标签的内联样式,但在这里,文字颜色为蓝色,这个样式被写进了style标签内,style熟悉无法获取到元素的真实样式,所以,需要通过getComputedStyle来获取真实的样式,这是一个全局属性
-
样式与行为分离
说明:对于图片中的1写法,如果我们需要在事件发生时,对一个元素添加多个样式,那就意味着我们要写许多个style属性进去,这样代码太复杂;前面提过,样式与行为分离,这样一来,我们可以先将对应的样式写好并添加一个class,然后通过js往这个元素上加一个class,就可以实现效果
mouseover、mouseout、mouseenter、mouseleave
input相关事件
-
输入框focus、blur
focus状态下输出1
blur状态下输出2 -
展现输入框内容并且转换为大写
-
通过keyboardEvent查看用户所按按键
-
change 当输入框内容发生变化时
输入内容时,内容在改变,但并未执行函数
输入完,点击页面,执行函数,控制台输出内容。那么change和blur有什么区别呢?对于blur,只要输入框失焦了,都会去执行函数;而对于change,可以理解为两个过程,首先输入框失焦,然后判断输入框的内容和之前是否发生了改变,只有发生了改变,才会执行函数。 -
form表单的提交时间
需求:例如,做一个登录或注册的页面,填完表单后,提交并进行数据的验证
-
scroll事件
load事件(图片加载完成)
对于一些图片,我们不知道它的尺寸,只有在图片加载完成时,才能通过js去访问到,所以这时可以使用load事件
触摸事件
ontouchstart, ontouchend, ontouchmove
键盘事件:
onkeydown, onkeyup, onkeypress
页面相关事件:
onload, onmove(浏览器窗口被移动时触发), onresize(浏览器的窗口大小被改变时触发), onscroll(滚动条位置发生变化时触发)
表单相关事件
onblur(元素失去焦点时触发), onchange(元素失去焦点且元素内容发生改变时触发), onfocus(元素获得焦点时触发), onreset(表单中reset属性被激活时触发), onsubmit(表单被提交时触发);oninput(在input元素内容修改后立即被触发,兼容IE9+)
编辑事件
onbeforecopy:当页面当前的被选择内容将要复制到浏览者系统的剪贴板前触发此事件;
onbeforecut:当页面中的一部分或者全部的内容将被移离当前页面[剪贴]并移动到浏览者的系统剪贴板时触发此事件;
onbeforeeditfocus:当前元素将要进入编辑状态;
onbeforepaste:内容将要从浏览者的系统剪贴板传送[粘贴]到页面中时触发此事件;
onbeforeupdate:当浏览者粘贴系统剪贴板中的内容时通知目标对象;
oncontextmenu:当浏览者按下鼠标右键出现菜单时或者通过键盘的按键触发页面菜单时触发的事件;
oncopy:当页面当前的被选择内容被复制后触发此事件;
oncut:当页面当前的被选择内容被剪切时触发此事件;
onlosecapture:当元素失去鼠标移动所形成的选择焦点时触发此事件;
onpaste:当内容被粘贴时触发此事件;
onselect:当文本内容被选择时的事件;
onselectstart:当文本内容选择将开始发生时触发的事件;
拖动事件
ondrag:当某个对象被拖动时触发此事件 [活动事件];
ondragdrop:一个外部对象被鼠标拖进当前窗口时触发;
ondragend:当鼠标拖动结束时触发此事件;
ondragenter:当对象被鼠标拖动的对象进入其容器范围内时触发此事件;
ondragleave:当对象被鼠标拖动的对象离开其容器范围内时触发此事件;
ondragover:当某被拖动的对象在另一对象容器范围内拖动时触发此事件;
ondragstart:当某对象将被拖动时触发此事件;
ondrop:在一个拖动过程中,释放鼠标键时触发此事件;
自定义事件
var EventCenter = {
on: function(type, handler){
document.addEventListener(type, handler)
},
fire: function(type, data){
return document.dispatchEvent(new CustomEvent(type, {
detail: data
}))
}
}
EventCenter.on('hello', function(e){
console.log(e.detail)
})
EventCenter.fire('hello', '你好')