books

异步编码:处理事件

★引子

在写popover组件的时候,我发现我对事件的认识,始终有点模糊!因此,我打算重新认识一下事件!

★前言

你以前编写的代码执行:从上到下按顺序执行,说白了就是按部就班的执行呗!

然而,JavaScript代码通常不是这样编写的,大多数JavaScript代码都是事件响应式的

那都有什么样的事件呢?——用户单击网页、通过网络收到数据、定时器到期、DOM发生变化等,事实上,在浏览器中,幕后始终有各种事件在不断发生

从同步到异步的转变,我没想到DOM发生变化也是一种事件,或许这是UI更新事件吧!

★学习目标

★收获

★开始认识

◇动脑?

浏览器幕后会执行以下任务:

如果浏览器可以在这些事件发生时发出通知,即通知你开发者去响应这些事件,那么你会写出怎样有趣的代码来呢?

◇事件是什么?

1564645982281

那么事件是什么呢?——浏览器幕后为你做的那些事儿,如同管家一样

有哪些事件呢?

➹:事件参考 - MDN

事件对于我们编码而言就是,每当有事件发生时,你都可以在代码中处理它

◇事件处理程序是什么

它指的是事件处理程序

◇如何创建第一个事件处理程序

要理解事件,最佳方式是编写一个处理程序,并将其关联到一个事件

如何为网页加载事件添加个callback呢?

前提条件:你得知道网页加载事件的触发时间点是:浏览器加载完网页、显示网页的所有内容并生成了表示网页的DOM。(其实DOM构建结束、然后内容都加载完毕后触发!)

  1. 编写一个处理网页加载事件的函数,功能是在网页加载完毕时alert一下,对了,你还得取个醒目的名字 ,如pageLoadedHandler

    1564654756293

  2. 处理程序编写好后,需要建立关联,让浏览器知道有这么一个函数,应在加载事件发生时调用它

    1564655214829

    我觉得这种姿势建立关联是很不好的!因为这样改变了全局属性的值,不过我查看了onload的初始值,发现它是 null值,这给了我一个启示是事件总是会发生的,如果你给了 onload一个callback,那么它就是有值的,而这个值就会让你看到,onload事件时有触发的,并且我们给它的callback是有执行的。

  3. 编写这些代码后,我们就知道网页加载完毕后,浏览器将调用赋给属性window.onload的函数。

◇测试事件处理程序

1564655700938

注意:是响应回来的一点内容,浏览器就会解析一点内容!如果遇到图片、css、js文件,那么就会去请求它们,等这些资源下载并加载完毕后,才会去执行关联load事件的callback!

◇考考你的脑力?

在不编写函数的情况下,能否指定事件处理程序?

函数就是事件处理程序呀?你不编写函数,哪里的callback啊?

★通过创建一个游戏来理解事件

理解事件的最佳方式是实践,因此我们用事件来规划游戏。


★总结

★Q&A

①事件?

在这里事件指的是事件驱动程序设计(一种计算机程序设计模型)。

在计算机编程中,事件驱动编程是一种编程范式,其中程序的流程由用户动作(鼠标点击、按键)、传感器输出或来自其他程序或线程的消息等事件决定。事件驱动编程是图形用户界面和其他应用程序(例如,JavaScript Web应用程序)中使用的主要范例,这些应用程序以响应用户输入而执行某些动作为中心

总之,程序的运行流程是由用户决定的!而不是我们程序员决定的!

➹:事件驱动程序设计 - 维基百科,自由的百科全书

②onload、DOMContentLoaded?

HTML页面的生命周期有以下三个重要事件:

每个事件都有特定的用途

➹:[译]页面生命周期:DOMContentLoaded, load, beforeunload, unload解析 · Issue #3 · fi3ework/blog

③有哪些代码编写方式?

我所了解到的:

➹:源代码怎么编写的? - 知乎

➹:编写傻瓜式的代码 – 腊八粥 – 一个关于计算机、极客、阅读和写作的英文文章的翻译网站

➹:函数式编程的代码怎么组织? - 知乎

➹:编程中,有哪些好的习惯从一开始就值得坚持? - 知乎

➹:编程的几个思想 - 知乎

➹:组织代码的四大策略

➹:你是如何去组织项目中的 Less/Sass 代码的? - 知乎

➹:如何写好一个类? - 知乎

➹:如何写好一个parser? - 知乎

➹:如何写出优美的 JavaScript 代码? - 知乎

➹:如何写出高水平的前端代码? - 知乎

➹:作为前端工程师,你认为写过的或你看到过的最好的前端代码是什么? - 知乎

➹:如何写出优秀的代码注释? - 知乎

④如何编写函数?

➹:提高代码质量:如何编写函数 - 狼狼的蓝胖子

⑤target、this、currentTarget区别?

  1. target:触发事件的某个具体对象,只会出现在事件流的目标阶段,谁触发谁命中

  2. currentTarget:绑定事件的对象,恒等于this,可能出现在事件流的任意一个阶段中

  3. 通常情况下target和currentTarget是一致的,我们只要使用target即可。但是有一种情况,必须区分三者之间的关系:

    父子嵌套关系中,父元素绑定了事件,然而单击子元素触发事件,则根据事件流,在布阻止事件流的前提下他会传递至父元素,因为他是绑定事件的对象,而target由于是触发事件的具体对象,它会指向子元素

➹:target、this、currentTarget区别 - 简书

总之target是触发事件的那个对象,而currentTarget则是那个冒泡上真正绑定了事件的对象。

<div id="one">
	<div id="three"></div>
</div>
one.addEventListener('click',function(e){
	console.log(e.target);  //three
	console.log(e.currentTarget);  //one
},false);
function delBtnHandle() {
    //通过当前元素父节点的父节点的第一个子节点的文本内容确定当前数组的属性并删除
    //这里不能用e,要用this来指代对象
    var cityName = this.target.parentNode.parentNode.childNodes[0].innerHTML;
    delete aqiData[cityName];
    renderAqiList();
}

⑥为啥document这个对象可以被点击?

Document 接口表示任何在浏览器中载入的网页,并作为网页内容的入口,也就是DOM 树。DOM 树包含了像 bodytable 这样的元素,以及大量其他元素。它向网页文档本身提供了全局操作功能,能解决如何获取页面的 URL ,如何在文档中创建一个新的元素这样的问题。

1564719917635

Document 接口描述了任何类型的文档的通用属性与方法。根据不同的文档类型(例如HTMLXMLSVG,…),还能使用更多 API:使用 "text/html" 作为内容类型(content type)的 HTML 文档,还实现了 HTMLDocument 接口,而 XML 和 SVG 文档则(额外)实现了 XMLDocument 接口。

➹:Document - Web API 接口参考 - MDN

关于JS的接口概念:

继承是OO语言中的一个最为人津津乐道的概念.许多OO语言都支持两种继承方式: 接口继承实现继承 .接口继承只继承方法签名,而实现继承则继承实际的方法.由于js中方法没有签名,在ECMAScript中无法实现接口继承.ECMAScript只支持实现继承,而且其 实现继承 主要是依靠原型链来实现的.

Node继承了EventTarget接口,那么意味着EventTarget会多了一些功能!

➹:JS原型链与继承别再被问倒了 - 掘金

⑦事件委托?

解决什么痛点?——很多个子元素需要绑定同一个事件,而其中的callback费内存呀!

原理:利用了事件冒泡,把事件委托给父元素绑定

写一个事件委托?

function eventDelegate (parentSelector, targetSelector, events, foo) {
  // 触发执行的函数
  function triFunction (e) {
    // 兼容性处理
    var event = e || window.event;

    // 获取到目标阶段指向的元素
    var target = event.target || event.srcElement;

    // 获取到代理事件的函数
    var currentTarget = event.currentTarget;

    // 处理 matches 的兼容性
    if (!Element.prototype.matches) {
      Element.prototype.matches =
        Element.prototype.matchesSelector ||
        Element.prototype.mozMatchesSelector ||
        Element.prototype.msMatchesSelector ||
        Element.prototype.oMatchesSelector ||
        Element.prototype.webkitMatchesSelector ||
        function(s) {
          var matches = (this.document || this.ownerDocument).querySelectorAll(s),
            i = matches.length;
          while (--i >= 0 && matches.item(i) !== this) {}
          return i > -1;            
        };
    }

    // 遍历外层并且匹配
    while (target !== currentTarget) {
      // 判断是否匹配到我们所需要的元素上
      if (target.matches(targetSelector)) {
        var sTarget = target;
        // 执行绑定的函数,注意 this
        foo.call(sTarget, Array.prototype.slice.call(arguments))
      }

      target = target.parentNode;
    }
  }

  // 如果有多个事件的话需要全部一一绑定事件
  events.split('.').forEach(function (evt) {
    // 多个父层元素的话也需要一一绑定
    Array.prototype.slice.call(document.querySelectorAll(parentSelector)).forEach(function ($p) {
      $p.addEventListener(evt, triFunction);
    });
  });
}

这个事件委托不是一下子就能写好的,借用《重构与模式》中有一句非常经典的话:“如果想成为一名更优秀的软件设计师,了解优秀软件设计的演变过程比学习优秀设计本身更有价值,因为设计的演变过程中隐藏着大智慧。

因此,了解这个事件委托写法的演变过程比看这个最终的事件委托要好得多!

总之,我们在学习中,也许可以从一门技术的演变中,得到灵感,然后创造出更好的技术!

DOM结构:

<ul id="list">
  <li><span>item 1</span></li>
  <li><span>item 2</span></li>
</ul>

使用:

eventDelegate('#list', 'li', 'click', function () { console.log(this); });

局限性:

focus、blur 之类的事件本身没有事件冒泡机制,所以无法委托;

mousemove、mouseout 这样的事件,虽然有事件冒泡,但是只能不断通过位置去计算定位,对性能消耗高,因此也是不适合于事件委托的;

➹:实例分析JavaScript中的事件委托和事件绑定 - “地瓜哥”博客网

➹:JavaScript 事件委托详解 - 知乎

➹:js事件委托详解 - 个人文章 - SegmentFault 思否