Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

js事件详解 #30

Open
aermin opened this issue Feb 26, 2018 · 0 comments
Open

js事件详解 #30

aermin opened this issue Feb 26, 2018 · 0 comments
Labels

Comments

@aermin
Copy link
Owner

aermin commented Feb 26, 2018

javaScript 与 HTML 之间的交互是通过事件实现的。

事件流

概念:事件流描述的是从页面中接收事件的顺序。

事件冒泡

概念:即事件开始时由最具体的元素(文档中嵌套层次最深 的那个节点)接收,然后逐级向上传播到较为不具体的节点(文档)。

image

理论及运用

  • 任何可以冒泡的事件都不仅仅可以在事件目标上进行处理,目标的任何祖先节点上也能处理。

运用事件委托 更详细的介绍看这篇文章

事件委托原理:以将事件处理程序附加到更高层的地方负责多个目标的事件处理。

大多数 Web 应用在用户交互上大量用到事件处理程序。页面上的事件处理程序的数量和页面响应 用户交互的速度之间有个负相关。为了减轻这种惩罚,最好使用事件代理。

事件委托实例

<ul id="list">  
        <li>我是孩子1</li>  
        <li>我是孩子2</li>  
        <li>我是孩子3</li>  
        <li>我是孩子4</li>  
        <li>我是孩子5</li>  
</ul> 
<div id = "btn">添加一个新孩子</div>

场景1:需要在点击列表项的时候响应一个事件。如果给每个列表项一一都绑定一个函数,那对于内存消耗是非常大的,效率上需要消耗很多性能。因此,比较好的方法就是把这个点击事件绑定到他的父层,也就是 ul 上,然后在执行事件的时候再去匹配判断目标元素;

场景2:动态添加新的Li元素。在很多时候,我们需要通过 AJAX 或者用户操作动态的增加或者去除列表项元素,那么在每一次改变的时候都需要重新给新增的元素绑定事件,给即将删去的元素解绑事件。如果用了事件委托就没有这种麻烦了,因为事件是绑定在父层的,和目标元素的增减是没有关系的,执行到目标元素是在真正响应执行事件函数的过程中去匹配的;

代码: 代码运行

//事件委托
var ul = document.getElementById('list'); 
ul.addEventListener('click', function (event) {
  // 兼容性处理
  var event = event||window.event;
  var target = event.target || event.srcElement;//target表示在事件冒泡中触发事件的源元素,在IE中是srcElement  
  // 判断是否匹配目标元素
  if (target.nodeName.toLowerCase() == 'li') {
       alert(target.innerHTML);
  }
});
//添加li元素
document.getElementById('btn').addEventListener('click', function (event) {
        var li = document.createElement('li');  
        li.innerHTML="我是新孩子";  
        ul.appendChild(li);  
})
  • 不是所有的事件都能冒泡。以下事件不冒泡:blur、focus、load、unload。

  • 有些业务场景需要阻止冒泡事件的冒泡,比如在一个HTML结构中,父级包含着子级,当事件在子级发生时(click,mouseenter,mouseleave等),由于事件冒泡就会触发父级的同名事件。示例:

<ul id="parent">
    <li id="child">son</li>
</ul>
   child.addEventListener("click",function(event){
        alert(1);
        // event stopPropagation(); //标准浏览器
        //或者是
        //event cancelBubble = true; //IE
    })
    parent.addEventListener("click",function(event){
        alert(1);
    })

当点击li时,会弹出两个1,可以通过阻止冒泡防止这样的行为,点击li就弹出一个1。
event.stopPropagation()或event.cancelBubble = true 可以阻止事件冒泡。

  • 阻止冒泡并不能阻止对象默认行为。比如submit按钮被点击后会提交表单数据,这种行为无须我们写程序定制。

应用

  • 事件代理,事件冒泡允许多个操作被集中处理(把事件处理器添加到一个父级元素上,避免把事件处理器添加到多个子级元素上),它还可以让你在对象层的不同级别捕获事件

  • 让不同的对象同时捕获同一事件,并调用自己的专属处理程序做自己的事情,就像老板一下命令,各自员工做自己岗位上的工作去了。

事件捕获

概念:事件捕获的思想 是不太具体的节点应该更早接收到事件,而最具体的节点应该最后接收到事件。事件捕获的用意在于在 事件到达预定目标之前捕获它。

image

DOM事件流

“DOM2级事件”规定的事件流包括三个阶段::事件捕获阶段、处于目标阶段和事件冒泡阶段。

首先发生的是事件捕获,为截获事件提供了机会。然后是实际的目标接收到事件。最后一个阶段是冒泡阶 段,可以在这个阶段对事件做出响应。

image

在 DOM 事件流中,实际的目标(

元素)在捕获阶段不会接收到事件。这意味着在捕获阶段, 事件从 document 到再到后就停止了。下一个阶段是“处于目标”阶段,于是事件在
上发生,并在事件处理(后面将会讨论这个概念)中被看成冒泡阶段的一部分。然后,冒泡阶段发生, 事件又传播回文档。

事件处理程序

概念:事件就是用户或浏览器自身执行的某种动作。诸如 click、load 和 mouseover,都是事件的名字。 而响应某个事件的函数就叫做事件处理程序(或事件侦听器)。事件处理程序的名字以"on"开头,onclick等。

HTML事件处理程序

概念:某个元素支持的每种事件,都可以使用一个与相应事件处理程序同名的 HTML 特性来指定。

例如,要在按钮被单击时执行一些 JavaScript,可以像下面 这样编写代码:

<input type="button" value="Click Me" onclick="alert(&quot;Clicked&quot;)" />

缺点

首先,存在一个时差问题。因为用户可能会在 HTML 元素一出现在页面上就触发相应的事件,但当时的事件处理程序有可能尚不具备执行条件。

另一个缺点是,这样扩展事件处理程序的作用域链在不同浏览器中会导致不同结果。

最后一个缺点是 HTML 与 JavaScript 代码紧密耦合。如果要更换事 件处理程序,就要改动两个地方:HTML 代码和 JavaScript 代码。

DOM0 级事件处理程序

简单来说就是取得一 个要操作的对象的引用,然后为它指定了某事件处理程序(如onclick)。使用 DOM0 级方法指定的事件处理程序被认为是元素的方法,以这种方式添加的事件处理程序会在事件流的冒泡阶段被处理。

var btn = document.getElementById("myBtn"); 
btn.onclick = function(){ 
    alert(this.id); //"myBtn" 
};

btn.onclick = null;//删除事件处理程序

将事件处理程序设置为 null 之后,再单击按钮将不会有任何动作发生。

DOM2 级事件处理程序

“DOM2 级事件”定义了两个方法,用于处理指定和删除事件处理程序的操作:addEventListener() 和 removeEventListener()。

onclick 事件会被覆盖,addevenlistener 事件先后执行。

所有 DOM 节点中都包含这两个方法,并且它们都接受 3 个参数:要处理的事件名、作为事件处理程序的函数和一个布尔值。最后这个布尔值参数如果是 true,表示在捕获 阶段调用事件处理程序;如果是 false,表示在冒泡阶段调用事件处理程序。没有指定的话,默认为 false。

target.addEventListener(type, listener, useCapture);
var btn = document.getElementById("myBtn"); 
btn.addEventListener("click", function(){ 
    alert(this.id); 
}, false);

上面的代码为一个按钮添加了 onclick 事件处理程序,而且该事件会在冒泡阶段被触发(因为最后一个参数是 false,此时和DOM0级的onclick事件差不多)。

使用 DOM2 级方法添加事件处理程序的主要好处是可以添加多个事件处理程序。

var btn = document.getElementById("myBtn");
btn.addEventListener("click", function(){
    alert(this.id); 
}, false); 
btn.addEventListener("click", function(){
    alert("Hello world!"); 
}, false);

这两个事件处理程序会按照添加它们的顺序触发,因此首先会显示元素的 ID,其次会显示"Hello world!"消息。

MDN 对第三个参数的解释是:Boolean,是指在DOM树中,注册了该listener的元素,是否会先于它下方的任何事件目标,接收到该事件。

沿着DOM树向上冒泡的事件不会触发被指定为为true的listener(因为此时为true是比冒泡更早的事件捕获阶段)。

例子: (引自csdn的一篇文章

20170502210420519

<style>
.parent
{
    position: relative;
    background-color: coral;
    border: 1px solid;
    padding: 50px;
}
.child {
    position: relative;
    background-color: pink;
    width: 100px;
    height: 100px;
}
</style>
<body>
<p>该实例演示了在添加事件监听时冒泡与捕获阶段的不同。</p>
<div id="parent" class="parent">
    <div id="child" class="child">点击该方块, 我是冒泡</div>
</div><br>
<div id="parent2" class="parent">
    <div id="child2" class="child">点击该方块, 我是捕获</div>
</div>
<script>
//事件冒泡    child的DOM事件先发生(粉),parent的DOM事件再发生(橙)。
document.getElementById("child").addEventListener("click", function(){
    alert("你点击了 粉色!");
}, false);
document.getElementById("parent").addEventListener("click", function(){
    alert("你点击了 橙色!");
}, false);

//事件捕获  parent的DOM事件先发生(橙),parent的DOM事件再发生(粉)。
document.getElementById("child2").addEventListener("click", function(){
    alert("你点击了 粉色!");
}, true);
document.getElementById("parent2").addEventListener("click", function(){
    alert("你点击了 橙色!");
}, true);
</script>
</body>

点击查看实例效果

大多数情况下,都是将事件处理程序添加到事件流的冒泡阶段,这样可以最大限度地兼容各种浏览器。最好只在需要在事件到达目标之前截获它的时候将事件处理程序添加到捕获阶段。如果不是特别需 要,我们不建议在事件捕获阶段注册事件处理程序。

事件对象

兼容 DOM 的浏览器会将一个 event 对象传入到事件处理程序中。无论指定事件处理程序时使用什 么方法(DOM0 级或 DOM2 级),都会传入 event 对象。

var btn = document.getElementById("myBtn"); 
btn.onclick = function(event){
    alert(event.type); //"click" 
}; 
btn.addEventListener("click", function(event){
alert(event.type); //"click" 
}, false);

触发的事件类型不一样,可用的属性和方法也不一样。常见的有event.type,event.target 等

事件类型

Web 浏览器中可能发生的事件有很多类型。如前所述,不同的事件类型具有不同的信息,而“DOM3 级事件”规定了以下几类事件。

  • UI(User Interface,用户界面)事件,当用户与页面上的元素交互时触发; 如resize,scroll
  • 焦点事件,当元素获得或失去焦点时触发; 如blur (在元素失去焦点时触发,这个事件不会冒泡)
  • 鼠标事件,当用户通过鼠标在页面上执行操作时触发;如click
  • 滚轮事件,当使用鼠标滚轮(或类似设备)时触发;
  • 文本事件,当在文档中输入文本时触发;
  • 键盘事件,当用户通过键盘在页面上执行操作时触发;
  • 合成事件,当为 IME(Input Method Editor,输入法编辑器)输入字符时触发;
  • 变动(mutation)事件,当底层 DOM 结构发生变化时触发。

内存和性能

事件委托

这个前面讲到了 ,这边不重复。

移除事件处理程序

每当将事件处理程序指定给元素时,运行中的浏览器代码与支持页面交互的 JavaScript 代码之间就 会建立一个连接。。这种连接越多,页面执行起来就越慢。如前所述,可以采用事件委托技术,限制建立 的连接数量。另外,在不需要的时候移除事件处理程序,也是解决这个问题的一种方案。内存中留有那 些过时不用的“空事件处理程序”(dangling event handler),也是造成 Web 应用程序内存与性能问题的 主要原因。

在两种情况下,可能会造成上述问题。第一种情况就是从文档中移除带有事件处理程序的元素时。 这可能是通过纯粹的 DOM 操作,例如使用 removeChild()和 replaceChild()方法。但更多地是发 生在使用 innerHTML 替换页面中某一部分的时候。如果带有事件处理程序的元素被 innerHTML 删除 了,那么原来添加到元素中的事件处理程序极有可能无法被当作垃圾回收。

<div id="myDiv"> 
    <input type="button" value="Click Me" id="myBtn"> 
</div>
<script type="text/javascript">
    var btn = document.getElementById("myBtn");
    btn.onclick = function(){
    //先执行某些操作
   btn.onclick = null; //移除事件处理程序
    document.getElementById("myDiv").innerHTML = "处理中..."; //把按钮替换成文字
}; 
</script>

这里,有一个按钮被包含在

元素中。为避免双击,单击这个按钮时就将按钮移除并替换成一 条消息;这是网站设计中非常流行的一种做法。但问题在于,当按钮被从页面中移除时,它还带着一个 事件处理程序呢。在
元素上设置 innerHTML 可以把按钮移走,但事件处理程序仍然与按钮保持 着引用关系。有的浏览器(尤其是 IE)在这种情况下不会作出恰当地处理,它们很有可能会将对元素和 对事件处理程序的引用都保存在内存中。如果你知道某个元素即将被移除,那么最好手工移除事件处理 程序

第二种情况,就是卸载页面的时候。如果在页面被卸载之前没 有清理干净事件处理程序,那它们就会滞留在内存中。每次加载完页面再卸载页面时(可能是在两个页 面间来回切换,也可以是单击了“刷新”按钮),内存中滞留的对象数目就会增加,因为事件处理程序 占用的内存并没有被释放。一般来说,最好的做法是在页面卸载之前,先通过 onunload 事件处理程序移除所有事件处理程序。 在此,事件委托技术再次表现出它的优势——需要跟踪的事件处理程序越少,移除它们就越容易。对这 种类似撤销的操作,我们可以把它想象成:只要是通过 onload 事件处理程序添加的东西,最后都要通 过 onunload 事件处理程序将它们移除。

模拟事件

概念:使用 JavaScript 在任意时刻来触发特定的事件,此时的事件就如同浏览器创建的事件一样。

模拟鼠标事件

思路: 1.创建事件对象 2.分发事件

          //创建事件对象
           var event = new MouseEvent('click', {
                cancelable: true,
                bubble: true,
                view: document.defaultView
           });
           //分发事件 
          document.getElementById("myBtn").dispatchEvent(event);

ps:红包书介绍的创建事件对象的方法有点过时了,现被MDN不建议使用

    var event = document.createEvent("MouseEvents");  //创建事件对象 
    event.initMouseEvent("click", true, true, document.defaultView, 0, 0, 0, 0, 0, false, false, false, false, 0, null);  //初始化事件对象 

image

实例运用 代码运行

这边有两个按钮,按钮1绑定一个鼠标点击事件1,alert出一句话;在按钮2绑定一个鼠标点击事件2,鼠标点击事件2将模拟用户去点击按钮1。所以点按钮2,触发一个模拟鼠标事件去点击按钮1。

<body>
    <button id="btn1">我是按钮1</button>
    <button id="btn2">我是按钮2,点我一下 按钮1也会被点击哦</button>
    <script type="text/javascript">
        var btn=document.getElementById("#btn1");
        // 按钮1的点击事件
        btn.addEventListener('click',function(e){
           console.log("我是按钮1,我被点击啦") // 点击按钮2,按钮1也会被点击(模拟事件);只点击按钮1(正常事件)
        })
        // 按钮2的点击事件
        document.querySelector("#btn2").addEventListener('click',function(e){
           //模拟鼠标事件步奏1  创建事件对象
           var event = new MouseEvent('click', {
                cancelable: true,
                bubble: true,
                view: document.defaultView
           });
           //模拟鼠标事件步奏2  触发事件 
          btn.dispatchEvent(event);
   })
      </script>
</body>

模拟键盘事件

这边举一反三,不做解释。

模拟其他事件

自定义 DOM 事件

实例运用 代码运行

<body>
    <button id="btn1">我是按钮1,我没鼠标事件,点我不会输出</button>
    <button id="btn2">我是按钮2,点我一下会执行绑定在按钮1的自定义事件,会有输出</button>
    <script type="text/javascript">
       var btn= document.querySelector("#btn1");
        // 按钮1的点击事件
        btn.addEventListener('test',function(e){
           console.log("我是按钮1,我被点击啦") // 点击按钮2,有输出(自定义事件);只点击按钮1,没输出(自定义事件,非模拟鼠标事件)
        })
       // 按钮2的点击事件
        document.querySelector("#btn2").addEventListener('click',function(e){
          //自定义事件步奏1  创建事件对象
           var event = new Event('test', {
                cancelable: true,
                bubble: true,
                view: document.defaultView
           });
           //自定义鼠标事件步奏2  触发事件 
          btn.dispatchEvent(event);
   })
      </script>
</body>

红宝书的方法有点过时,新实现方法可看这篇文章

@aermin aermin added the js label Feb 26, 2018
@aermin aermin changed the title js事件 js事件详解 Feb 27, 2018
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

1 participant