一、封装触发事件的函数
拜读了elementUI的dom.js后,感觉整个人好像打通了任督二脉,于是有感而发,想写一下关于里面的触发事件所学到的知识。
先说一下我看了哪个函数:
export const on = (function() {
if (!isServer && document.addEventListener) {
return function(element, event, handler) {
if (element && event && handler) {
element.addEventListener(event, handler, false);
}
};
} else {
return function(element, event, handler) {
if (element && event && handler) {
element.attachEvent('on' + event, handler);
}
};
}
})();
里面涉及到的准备要讲的:
- 函数立即执行
- addEventListener VS attachEvent
二、先了解一下基础知识
1、(函数声明 VS 函数表达式 VS 匿名函数) => 函数的立即执行
1 函数声明的写法
图1-1
如上图1-1,我们可以看到,上面这段以function关键字开头声明一个函数的,我们叫做函数声明。
函数声明有一个特点,就是js所谓的变量提升。js会在编译的时候把这种函数声明方式的函数先编译了,然后在执行阶段,即使fnName()写在fnName声明前面,也不会报错的。因为编译阶段它会把该函数写成下图1-2:
图1-2
2 函数表达式
图1-3
如上图1-3,是函数表达式的写法。
与函数声明不同的是,函数表达式只有在执行阶段执行到它的时候,才会执行,而在编译阶段是没有做任何事情的,也就是没有所谓的变量提升。所以如果写成下面这样:
<script>
/* oh,NO!fnName is undefined */
/* fnName没有被声明就调用了,js不知道fnName是什么 */
fnName();
let fnName = function() {
alert('zhiyuan');
}
</script>
那浏览器就会报错了!
当然,无论是函数声明还是函数表达式,其实我认为都应该要按顺序写下来,毕竟这是代码规范嘛~
3 匿名函数
图1-4
如上图1-4,匿名函数其实属于函数表达式的特殊方式。它常用于闭包或者函数立即执行(等下说)等,这里就不详细展开说了。
4 函数立即执行
js有一个很特别的地方,就是函数可以立即执行,而且写法很有趣。且看下图1-5:
图1-5
上图1-5就是函数立即执行的写法,它其实利用了匿名函数。函数立即执行有两种常用写法:
<script>
/* (function(){...})() */
(function(a){
alert(a);
})(123);
</script>
/* 或者 */
/* (function(){...}()) */
<script>
(function(a){
alert(a);
}(123));
</script>
那么,函数立即执行到底是什么呢?它其实就是写了一个函数表达式,然后立即执行,如下代码所示:
<script>
/* 函数立即执行相当于 */
var fnName = function(a) {
alert(a);
}(123);
</script>
/* 推理出如下 */
<script>
var fnName = function(a) {
alert(a);
};
fnName(123);
</script>
可以看出,其实a是函数的参数,而真正被传递的,是123。所以最终,浏览器会alert出123。
那么,函数立即执行还有其他写法?有的,下面给出的是函数立即执行的几种写法(这几种经常在笔试中看到):
<script>
/* (function(a){...}(123)) */
(function(a) {
alert(a);
}(123));
/* (function(a){...})(...) */
(function(a) {
alert(a);
})(123);
/* +运算符 */
+function(a){
alert(a);
}(123);
/* -运算符 */
-function(a){
alert(a);
}(123);
/* !运算符 */
!function(a){
alert(a);
}(123);
/* 函数表达式 */
let fnName = function(a){
alert(a);
}(123);
</script>
好,那函数作用域有什么意义呢?
实际上,函数作用域是创建了一个私人空间,在这个私人空间中,所有变量和方法都是私人的,只能在这个作用域中使用,那么这样就不会破坏污染全局的命名空间。
二、addEventListener VS attachEvent
说完函数的立即执行,再说说elementUI源码中封装on用到的addEventListener和attachEvent两个监听事件的函数。
先说说这两个函数的不同之处吧。
- IE8(包括IE8)以下的版本都不支持addEventListener,而必须使用attachEvent兼容(有趣的是:IE11不支持attachEvent了,转而支持addEventListener)
- addEventListener接受三个参数,attachEvent接受两个参数
- addEventListener的第一个参数不需要带上on(addEventListener('click'),click前面不用加on);attachEvent就要加上on(attachEvent('onclick'))
1 addEventListener
先说一下addEventListener这个函数吧。addEventListener接受三个参数
- event——必选参数。事件(click, mouseover等)。
- callback——必选参数。监听到事件后的回调函数。
- useCapture——可选。布尔值,指定事件是否在捕获或冒泡阶段执行。
用法:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>addEventListener VS attachEvent</title>
</head>
<body>
<div id="div1">
<button id="btn1">汁源</button>
</div>
<script>
/* querySelector只能在IE8(包括IE8)以上版本才能使用 */
var div1 = document.querySelector('#div1');
var btn1 = document.querySelector('#btn1');
/* addEventListener支持IE11和其他浏览器 */
/* 此时点击按钮,先alert出“我是btn1”,再alert出“我是div1” */
div1.addEventListener('click', function() {
alert('我是div1');
}, false);
btn1.addEventListener('click', function() {
alert('我是btn1');
}, false);
</script>
</body>
</html>
前面两个参数我们就不多说了,关键看第三个参数useCapture。useCapture接收的是布尔值,当为false的时候,表示采用冒泡方式。即事件由里到外进行冒泡。
所以,这个时候如果点击button这个元素的话,那么就会先alert出“我是btn1”,然后再alert出“我是div1”。
相反,如果useCapture为true时,表示采用捕获方式,即先触发外层html元素的事件,然后再触发里层的。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>addEventListener VS attachEvent</title>
</head>
<body>
<div id="div1">
<button id="btn1">汁源</button>
</div>
<script>
/* querySelector只能在IE8(包括IE8)以上版本才能使用 */
var div1 = document.querySelector('#div1');
var btn1 = document.querySelector('#btn1');
/* addEventListener支持IE11和其他浏览器 */
/* 此时点击按钮,先alert出“我是div1”,再alert出“我是btn1” */
div1.addEventListener('click', function() {
alert('我是div1');
}, true);
btn1.addEventListener('click', function() {
alert('我是btn1');
}, true);
</script>
</body>
</html>
此时,如果点击button这个元素的话,那么就会先alert出“我是div1”,然后再alert出“我是btn1”。
2 attachEvent
attachEvent只可以在IE10(包括IE10)以下版本使用。它接收两个参数:
- event——事件
- callback——回调函数
用法:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>addEventListener VS attachEvent</title>
</head>
<body>
<button id="btn1">汁源</button>
<script>
var btn1 = document.getElementById('btn1');
/* 只能在IE10(包括IE10)以下的版本使用,其他浏览器不支持attachEvent */
btn1.attachEvent('onclick', function() {
alert(123);
});
</script>
</body>
</html>
注意:attachEvent方法是要带上on这个字符串的。
由此可见,写一个健壮性的触发事件的函数,需要浏览器兼容性问题。
现在,有了上面的基础,我们可以开始分析elementUI是怎么实现这个方法的。
三、elementUI的on函数
export const on = (function() {
/* isServer是判断现在是否是浏览器环境,可以忽略掉它 */
/**
* 首先通过document.addEventListener判断一下浏览器是否支持addEventListener
* 是的话就返回一个function(){addEventListener...},把这个function赋值给on这个变量
*/
if (!isServer && document.addEventListener) {
return function(element, event, handler) {
if (element && event && handler) {
element.addEventListener(event, handler, false);
}
};
} else {
/**
* 否则返回一个function(){attachEvent...},把这个function赋值给on这个变量
*/
return function(element, event, handler) {
if (element && event && handler) {
element.attachEvent('on' + event, handler);
}
};
}
})();
首先,看到它是以(function(){...})(...)执行的,证明它是一个立即执行的函数,执行结果是:
export const on = function(element, event, handler) {
if (element && event && handler) {
element.addEventListener(event, handler, false);
}
};
/* 或者 */
export const on = function(element, event, handler) {
if (element && event && handler) {
element.attachEvent('on' + event, handler);
}
};
那么这样,我们要是用on函数(on函数就是一个函数表达式)的话,就可以如下:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>addEventListener VS attachEvent</title>
</head>
<body>
<button id="btn1">汁源</button>
<script>
var on = (function() {
if (document.addEventListener) {
return function(element, event, handler) {
if (element && event && handler) {
element.addEventListener(event, handler, false);
}
};
} else {
return function(element, event, handler) {
if (element && event && handler) {
element.attachEvent('on' + event, handler);
}
};
}
})();
var btn1 = document.getElementById('btn1');
/**
* on
* @param element——触发事件的元素
* @param event——所触发的事件类型(click, mouseover等)
* @param handler——事件触发后的回调函数
*/
on(btn1, 'click', function() {
alert('zhiyuan');
})
</script>
</body>
</html>
好,基本上一个事件触发的封装就到此结束了!
如果文章有哪里写的不对或者大神们有高见指导的话,希望能高抬贵手留下您宝贵的留言,本人愿意洗耳恭听!
好了,我要继续读elementUI源码提升自己,希望大家共勉!