Go to comments

JavaScript 事件(上)

事件是前端语言或者说是整个互联网交互体验的核心,


什么是交互体验,交互体验要明白一个词什么是交互?

交互是你对程序或者对页面做了点什么小动作,然后页面返回给你一个效果。


一定是你做了什么事,页面给你一个反馈这个才叫交互,得两个人配合。你不动,页面自己动那不叫交互,比如"轮播图"就不叫交互,它自己就能动,交互一定是你动页面一下,页面给你一个回馈。

"事件操作"就是实现"交互功能"的核心的技术点。


一、什么叫事件?

我们人就有很多个事件,比如教父老邓天生有很多功能,你打他一拳他会给你一个回馈,踢一脚他也会给你一个回馈,训他一句他也会给你一回馈,这些给你的回馈不用管,凡是这种被打、被踢、被训的这种都叫做老邓的一个事件,天生存在的事件,事件说白了就是一个动作。


写一个div,然后选出来

<div style="width:100px;height:100px;background-color:red;"></div>

<script>

    var div = document.getElementsByTagName('div')[0];
    
</script>


首先,这个div现在能被我们点击吗?

干嘛不能,我就点击了又能怎么样,虽然没效果,事件未必追求效果,

比如老邓站那,打他一拳,他没反应也是打了,他也有一个属性是我能被挨打,

这个div方块也是,没效果也正常点击了,这种都叫做事件。


事件由两部分构成,

1. div.onclick 叫可以被点击的事件,一旦这个事件被触发之后,

2. 要执行后面的函数 function(){} 这叫反馈,前面的div.onclick事件叫点击事件

<div style="width:100px;height:100px;background-color:red;"></div>

<script>

  var div = document.getElementsByTagName('div')[0];

  div.onclick = function(){
    console.log('a');
  }

</script>

当div被点击之后要输出一个a

image.png


没写js语句的div也能被点击,要清楚这个概念,写了js语句之后,div被点击的时候会产生一个效果执行输出a,这叫事件触发的一个函数


演示demo,

拖拽是后面的一个例子


二、如何绑定事件


事件首先分为有很多种事件类型,

什么点击事件、移动事件、覆盖事件,键盘事件好多种事件类型,今天还讲不到事件类型,就用一个点击事件,绑定事件基本上分为三种方法:


1、ele.onxxx = function (event) {}

用第一种方法,给div绑定一个事件

<div style="width:100px;height:100px;background-color:red;"></div>

<script>

  var div = document.getElementsByTagName('div')[0];

  div.onclick = function(){

  }

</script>

在这个div的click事件上绑定了一个function函数,

我们说绑定事件,不是说给div绑定一个什么事件,div事件不绑定也有,我们绑定的只是事件处理函数


第一种写法就是用on 加上一个事件类型

click事件就是onclick,

mousedown就是onmousedown...


下面代码的意思是,在这个div的click事件上,绑定一个处理函数function,然后在函数里可以写上任意的东西,比如写上点击完div背景变成黄色的

<div style="width:100px;height:100px;background-color:red;"></div>

<script>

  var div = document.getElementsByTagName('div')[0];

  div.onclick = function(){
    this.style.backgroundColor = 'yellow';
  }

</script>


这是第一种绑定方式叫on的方式来绑定,这种绑定方式兼容性特别好,最早的处理事件绑定函数的方法就是这么写的,因为能感觉出它的蛮荒气息(写起来很low),

这就是给一个对象上加属性或是方法,但之前没有那么多高端办法,只能拿这种形式当做对象的事件来使用,

它的兼容性非常非常好的,但是它也有一个缺陷,div上面的click事件只能绑定一个处理函数。


如果再想绑定一个新的函数,想点击div的时候有出来一个a后又出来一个b

<div style="width:100px;height:100px;background-color:red;"></div>

<script>

  var div = document.getElementsByTagName('div')[0];

  div.onclick = function(){
    console.log('a');
  }

  div.onclick = function(){
    console.log('b');
  }

</script>

这样写法是不能出来一个a后,又出来一个b,因为属性赋值的语法是,只要赋一个新值就覆盖掉原来的值

换句话说虽然是事件,但本质上还是属性赋值,所以一个对象的一个事件只能绑定一个处理函数,这是它的一个小缺陷,这种写法基本等同于把事件写在HTML的行间上


在行间和上面等同的写法应该怎么写?

不用写function直接写执行语句就可以,把js写到行间行上面的效果完全一致,点击div后背景变成黄色

<div onclick="this.style.backgroundColor='yellow'" style="width:100px;height:100px;background-color:red;"></div>


这叫句柄的写法,所以我们管on这种绑定事件的方式叫句柄的绑定方式

<div onclick="console.log('A')" style="width:100px;height:100px;background-color:red;"></div>


2、ele.addEventListener(type, fn, false);

第二种绑定方式稍微高端一点,也是最正式的一种绑定方式叫addEventListener,IE9以下不兼容所以它是W3C标准的,只要IE9以下不兼容基本上都是W3C标准的,


elem.addEventListener( "事件类型", "处理函数", false )


三个参数

1. 事件类型:比如绑定一个click类型的怎么写?不写onclick直接写click(第一种绑定事件也说了on + click)。

2. 事件处理函数:可以写一个事件处理函数的引用function (){}

3. 第三个参数:第三个参数别管,先填false(先不管就有后续的故事)

<div style="width:100px;height:100px;background-color:red;"></div>

<script>

  var div = document.getElementsByTagName('div')[0];

  div.addEventListener('click', function (){

  }, false);

</script>


第二个参数事件处理函数function (){},写出来是一个匿名函数,这是函数体 还是 函数引用?

应该叫函数引用,匿名函数和在外面定义一个函数(函数名是test),直接把函数名test写在里面是一样的

<div style="width:100px;height:100px;background-color:red;"></div>

<script>

  var div = document.getElementsByTagName('div')[0];

  div.addEventListener('click', test, false);

  function test(){

  }

</script>


然后在test函数里面写上一条语句 console.log('a')

<div style="width:100px;height:100px;background-color:red;"></div>

<script>

  var div = document.getElementsByTagName('div')[0];

  div.addEventListener('click', function (){
    console.log('a');
  }, false);

</script>

这也是一个事件处理函数,当div被点击触发的时候能返回test函数

image.png


每点击一次执行一次,永远没有失效的时候,问一个问题,事件是不是时刻有一个监听的机制,就好像一个人始终在这等着点击,点击完后就执行了这叫事件监听机制,Listener是监听者的意思


这种事件监听机制需要耗费资源,能是js引擎做的吗?

js引擎要时刻监听就做不了其它的事了,一定也是其它部分做的,当它监听到之后要触发事件了,它会把任务放到执行队列里面就像setInterval,放到执行队列里面等待被执行。


addEventListener的优点是,它可以给一个对象的一个事件绑定多个处理函数

<div style="width:100px;height:100px;background-color:red;"></div>

<script>

  var div = document.getElementsByTagName('div')[0];

  div.addEventListener('click', function (){
    console.log('a');
  }, false);

  div.addEventListener('click', function (){
    console.log('b');
  }, false);

</script>

这是一个对象的一个事件的两个处理函数,点击后会触发a和b,并且按按照绑定的顺序去执行

image.png


如果再写一个事件绑定都输出"a"呢,这是两个函数还是同一个函数?

<div style="width:100px;height:100px;background-color:red;"></div>

<script>

  var div = document.getElementsByTagName('div')[0];

  div.addEventListener('click', function (){
    console.log('a');
  }, false);

  div.addEventListener('click', function (){
    console.log('a');
  }, false);

</script>

执行输出两个a

image.png


addEventListener能给同一个对象、同一个事件,绑定多个处理函数。

什么是多个处理函数?就是两个处理函数是不一样的(地址是不一样的),上面这两个函数长的一样地址不一样。


下面这个执行的是同一个test函数,输出的是一个a!!!

<div style="width:100px;height:100px;background-color:red;"></div>

<script>

  var div = document.getElementsByTagName('div')[0];

  div.addEventListener('click', test, false);

  div.addEventListener('click', test, false);

  function test(){
    console.log('a');
  }

</script>

从这个问题可以考虑到一个事,不能给同一个函数绑定多次,重复的函数只能执行一次


3、ele.attachEvent('on' + type, fn);

第三种绑定方法是IE独有的  attachEvent("on + 事件类型",  "处理函数")  它绑定两个参数,没有麻烦的false

<div style="width:100px;height:100px;background-color:red;"></div>

<script>

  var div = document.getElementsByTagName('div')[0];

  div.attachEvent('onclick', function (){

    alert('a');

  });

</script>


attachEvent和addEventListener基本上是一致的,只不过attachEvent是IE9以下浏览器用的,唯一不同的是形式上少了第三个参数false,

attachEvent在运行上和addEventListener基本上查不多,attachEven一个对象的同一个事件也可以绑定多个处理函数并且attachEvent更强大,它一个对象上一个事件绑定同一个函数,如果绑定了多次它都能执行多次,

attachEvent在谷歌浏览器上没有,只能到IE9及以下浏览器测试。


4、小练习

使用原生js,addEventListener给每个li绑定一个click事件,输出他们的顺序

<ul>
  <li>a</li>
  <li>a</li>
  <li>a</li>
  <li>a</li>
</ul>

<script>

  var li = document.getElementsByTagName('li');

  for(var i = 0; i < li.length; i++){
    (function(j){
      li[j].addEventListener('click', function(){
        console.log(j);
      });
    }(i))
  }

</script>


这块要记住一个问题,绑定事件万万要记住,一旦事件出现在循环里面,就要考虑是否形成闭包,

当然了不是每次都这么写,绑定事件的处理函数里面用不到for循环里面的"i"那就不用考虑闭包。


三、事件处理程序的运行环境


研究一下事件处理函数的环境的问题,这个运行环境就是指的 this


1、ele.onxxx = function (event) {},程序this指向是dom元素本身

选择div元素绑定事件,onclick一下里面的this指向的是谁?

<div style="width:100px;height:100px;background-color:red;"></div>
    
<script>

  var div = document.getElementsByTagName('div')[0];

  div.onclick = function(){

    console.log(this); // <div style="width:100px;height:100px;background-color:red;"></div>

  }

</script>

div绑定的事件this,指向的就是div自己。

image.png


2、obj.addEventListener(type, fn, false),程序this指向是dom元素本身

addEventListener的环境,this也指向它自己

<div style="width:100px;height:100px;background-color:red;"></div>
    
<script>

  var div = document.getElementsByTagName('div')[0];

  div.addEventListener('click', function(){

    console.log(this); // <div style="width:100px;height:100px;background-color:red;"></div>

  }, false);

</script>


3、obj.attachEvent('on' + type, fn),程序this指向window

唯独有点特殊的是谷歌浏览器演示不了的attachEvent(),它里面的this指向的不是自己,这块可以理解成一个bug了,正常来说应该和其它绑定功能高度一至的,可它就偏偏指向了window

<div style="width:100px;height:100px;background-color:red;"></div>

<script>

  var div = document.getElementsByTagName('div')[0];

  div.attachEvent('onclick', function(){

    console.log(this); // [object Window]

  });

</script>

attachEvent()里面打印this指向的是window,

image.png

但是我们不想要window这个结果,我们想在绑定事件里面写this,肯定想让this指向元素自己,this有不可取代性。


必须让attachEvent()执行的时候,里面的this指向div怎么办呢?

<div style="width:100px;height:100px;background-color:red;"></div>

<script>

  var div = document.getElementsByTagName('div')[0];

  div.attachEvent('onclick', function(){ // 2. 这个函数只是作为执行handle的处理函数,然后把真正的代码写到handle里面去

    handle.call(div); // 3. "handle.call(div)"这样把div传进去就解决this的问题了

  });

  function handle(){ // 1. 把事件处理函数写到外部

    this.style.backgroundColor = "yellow"; // 4. 因为"handle.call(div)",所以这里面的this就绝对指向div
  }

</script>


4、封装兼容性的 addEvent(elem, type, handle)方法

了解了事件绑定的三种方式和三种方式分别对应的环境,现在封装兼容性方法addEvent(elem, type, handle)来处理不同浏览器的兼容性,


给一个DOM对象添加该"事件类型"的处理函数,这样一个需求需要几个参数?

一个元素对象、事件类型、事件处理函数 这三个参数,这三个参数都是未知的都是灵活的,任意一个对象的任意一个事件类型的任意事件处理函数。


思路,给一个DOM对象添加该事件类型的处理函数,以addEventListener为标准有它就不用别的,如果不行折中一下用IE的attachEvent,如果这些都不行只能用蛮荒的气息的方法了elem.onclick=function(){}

<div style="width:100px;height:100px;background-color:red;"></div>

<script>

  /**
    * addEvent(elem, type, handle)兼容方法
    *
    * elem    object    元素
    * type    string    事件类型(是点击类型,还是键盘类型)
    * handle  function  处理函数  
    */

  function addEvent(elem, type, handle){

    if(elem.addEventListener){
      elem.addEventListener(type, handle, false);
    }else if(elem.attachEvent){
      elem.attachEvent('on' + type, function(){
        handle.call(elem);
      });
    }else{
      elem['on' + type] = handle;
    }

  }

  var div = document.getElementsByTagName('div')[0];

  addEvent(div, 'click', function(){

    this.innerHTML = "江湖儿女";
    this.style.backgroundColor = "orangered";
    this.style.color = "#fff";

  });

</script>

这就是最终的事件处理函数,我们用它可以更方便的兼容性最好的事件绑定过程,这个方法写到我们tools库里面(实测这个方法兼容到IE5.0)


四、解除事件处理程序


事件处理函数绑定了事件之后,我们未必希望事件一直绑定着,有时候我们还需要把这个事件解除,解除事件怎么解除呢?


1、ele.onclick = false/''/null

绑定完之后有一天有特殊需求,要求div元素不能有click事件了,这是很常见的绑定用完之后就不需要再有,怎么解除绑定?

绑定的机制就是加属性,解除的机制也是再加属性,直接让它等于null就没有了

<div style="width:100px;height:100px;background-color:red;"></div>

<script>

  var div = document.getElementsByTagName('div')[0];

  div.onclick = function(){
    console.log('a');
  }

  div.onclick = ''; // 解除绑定

</script>


这种解除绑定事件应用在哪?

举一个例子,有一些广告,一个网页一个生命周期里面只能触发一次,

比如太平洋下载有一个Download下载,点击完之后先弹出一个广告,然后再点就不出广告了


有些广告点完一次之后就不在出了,这样的效果我们也能做,只让它点击一次生效,点第二次就不生效了怎么办?

<div style="width:100px;height:100px;background-color:red;"></div>

<script>

  var div = document.getElementsByTagName('div')[0];

  div.onclick = function(){

    alert('只能点击一次,再点击不会在弹出来了');

    div.onclick = null; // 解除绑定写在绑定事件里面

  }

</script>

点击一次弹出提示框再点击就失效了,叫一次失效,只能执行一次的事件


2、ele.removeEventListener(type, fn, false);

如何解除addEeventListener事件?

解除的方法非常高端叫  removeEventListener(type, fn, false) 


解除 和 绑定高度一致,绑定写click解除就得写click,绑定写一个事件处理函数,解除就要写那个事件处理函数,然后写false

<div style="width:100px;height:100px;background-color:red;"></div>

<script>

  var div = document.getElementsByTagName('div')[0];

  div.addEventListener('click', function(){ // 这种写的是匿名函数,解除的时候清除不了

    alert('绑定了事件');

  }, false);

  div.removeEventListener('click', function(){}, false);

</script>


绑定的是一个对象的特定的事件类型的特定的处理函数,解除一定是对应的,对象要对应、事件类型要对应、处理函数要还是那个函数的引用,

如果我们绑定的是匿名函数,那就永远清除不了了,对象能找到、类型可以找到,匿名函数没法找。


如果想让一个事件处理函数能被清除,最好的方式是把函数写到外面,引用清除时候能找到它

<div style="width:100px;height:100px;background-color:red;"></div>

<script>

  var div = document.getElementsByTagName('div')[0];

  div.addEventListener('click', test, false);

  function test(){
    alert('绑定了事件');
  }

  div.removeEventListener('click', test, false); // 清除事件绑定

</script>


3、ele.detachEvent('on' + type, fn);

类似的attachEvent清除的方式是 ele.detachEvent('on' + type, fn) 和上面的方式类似,on+事件类型和一模一样的函数引用

<div style="width:100px;height:100px;background-color:red;"></div>

<script>

  var div = document.getElementsByTagName('div')[0];

  div.attachEvent('onclick', test);

  function test(){
    div.style.backgroundColor = "yellow";   
  }

  div.detachEvent('onclick', test); // 清除事件处理函数

  // 在IE10及IE10以下用

</script>


封装兼容性的解除函数意义不是很大,封装兼容性的绑定事件的方法非常有用尤其this指向,绑定的必须要背下来怎么去绑定函数,

下面是公开课里,讲的封装兼容性解绑函数


4、封装一个事件解绑函数(公开课的内容)

function removeEvent(dom, type, fn){

  if(window.removeEventListener){
    dom.removeEventListener(type, fn);
  }else if(window.detachEvent){
    dom.detachEvent('on' + type, fn);
  }else{
    dom['on' + type] = null;
  }

}



Leave a comment 0 Comments.

Leave a Reply

换一张