资讯专栏INFORMATION COLUMN

<<编写可维护的javascript>> 笔记7(事件处理)

microelec / 3371人阅读

摘要:在所有应用中事件处理都是非常重要的所有的均通过事件绑定到上所以大多数前端工程师需要花费很多时间来编写和修改事件处理程序遗憾的是在诞生之初这部分内容并未受太多重视甚至当开发者们开始热衷于将传统的软件架构概念融入到里时事件绑定仍然没有收到多大重

在所有JavaScript应用中事件处理都是非常重要的. 所有的JavaScript均通过事件绑定到UI上, 所以大多数前端工程师需要花费很多时间来编写和修改事件处理程序. 遗憾的是, 在JavaScript诞生之初, 这部分内容并未受太多重视. 甚至当开发者们开始热衷于将传统的软件架构概念融入到JavaScript里时, 事件绑定仍然没有收到多大重视. 大多数事件处理相关的代码和时间环境(对于开发者来说, 每次时间出发时才可能会用)紧紧耦合在一起, 导致可维护性很糟糕.

7.1 典型用法
多数开发者都很了解, 当事件出发时, 事件对象(event 对象) 会作为回调参数传入事件处理程序中. event对象包含所有和事件相关的信息, 包括事件的宿主(target) 以及其他和事件类型相关的数据. 鼠标事件会将其位置信息暴露在event对象上, 键盘事件会将按键信息暴露在event对象上, 触屏事件会将触摸位置和持续事件(duration) 暴露在event对象上, 只有提供了所有这些信息, UI才会正确地执行交互.

在很多场景中, 你只是用到了event所提供信息的一小部分, 看下面这段代码.

// 不好的写法
function handleClick(event) {
    var popup = document.getElementById("popup");
    popup.style.left = event.clientX + "px";
    popup.style.top = event.clientY + "px";
    popup.className = "reveal";
}

addListener(element, "click", handleClick);

这段代码只用到了event对象的两个属性: clientX和 clientY. 在将元素显示在页面里之前先用这两个属性给它作定位. 尽管这段代码看起来非常简单且没有什么问题, 但实际上是不好的写法, 因为这种做法有其局限性.

7.2 规则1: 隔离应用逻辑
上段实例代码的第一个问题是事件处理程序包含了应用逻辑(application logic). 应用逻辑是和应用相关的功能性代码, 而不是和用户行为相关的. 上段实例代码中, 应用逻辑实在特定位置显示一个弹出框. 尽管这个交互应当是在用户点击某个元素时发生的, 但情况并不总是如此.

将应用逻辑从所有时间处理程序中抽离出来的做法是一种最佳实践, 应为说不定什么事件其他地方就会触发同一段逻辑. 比如, 有时你需要在用户将鼠标移到某个元素上时判断是否显示弹出框, 或者当按下键盘上的某个键时也作同样的逻辑判断. 这样多个时间的处理程序执行的同样的逻辑, 而你的代码却被不小心赋值了多份.

将应用逻辑放置于事件处理程序中的另一个确定是和测试有关的. 测试时需要直接出发功能代码, 而不必通过模拟对元素点击来出发. 如果将应用逻辑放置于事件处理程序中, 唯一的测试方法是制造事件的触发. 尽管某些测试框架可以模拟触发事件, 但实际上这不是测试的最佳方法. 调用功能性代码最好的做法就是单个函数调用.

你总是需要将应用逻辑和事件处理的代码拆分开来. 如果要对上一段实例代码进行重构, 第一步是将处理弹出逻辑框的代码放入一个多带带的函数中, 这个函数很可能挂在与为该应用定义的一个全局对象上. 事件处理程序应当总是在一个相同的全局对象中, 因此就有了以下两个办法:

// 好的写法 - 拆分应用逻辑
var MyApplication = {
    handleClick: function(event) {
        this.showPopup(event);
    }
    
    showPopup: function(event) {
        var popup = document.getElementById("popup");
        popup.style.left = event.clientX + "px";
        popup.style.top = event.clientY + "px";
        popup.className = "reveal";
    }
};

addListener(element, "click", function(evnet) {
    MyApplication.handleClick(event);
})

之前在事件处理程序中包含了所有应用逻辑现在转移到了MyApplication.showPopup()方法中. 现在MyApplication.handleClick()方法制作一件事情, 即调用MyApplication.showPopup(). 若应用逻辑被剥离出去, 对同一段功能代码的调用可以在多点发生, 则不需要一定依赖于某个特定时间的触发, 这显然更加方便. 单着只是拆解时间处理程序代码的第一步.

7.3 规则2: 不要分发事件对象
在剥离出应用逻辑之后, 上段实例代码和存在一个问题, 即event对象被无节制地分发. 它从匿名的时间处理函数传入了MyApplication.handleClick(), 然后又传入了MyApplication.showPopup(). 正如上文提到的, event对象上包含很多和事件相关的额外信息, 而这段代码只用到了其中的两个而已.

应用逻辑不应当依赖于event对象来正确完成功能, 原因如下.

方法接口并没有表明哪些数据是必要的. 好的API一定是对于期望和依赖都是透明的. 将event对象作为参数并不能告诉你event的哪些属性时有用的, 用来干什么?

因此, 如果你想测试这个方法, 你必须重新创建一个event对象并将它作为参数传入. 所以, 你需要确切的知道张哥方法使用了哪些信息, 这样才能正切地写出测试代码.

这些问题在大型Web应用中都是不可取的. 代码不够明晰就会导致bug.

最佳的办法是让事件处理程序使用event对象来处理事件, 然后拿到所有需要的数据传给应用逻辑. 例如, MyApplication.showPopup()方法只需要两个数据, x坐标和y坐标. 这样我们将方法重写一下, 让它来接受这两个参数.

// 好的写法
var MyApplication = {
    handleClick: function(event) {
        this.showPopup(event.clientX, event.clientY);
    }
    
    showPopup: function(x, y) {
        var popup = document.getElementById("popup");
        popup.style.left = x + "px";
        popup.style.top = y + "px";
        popup.className = "reveal";
    }
};

addListener(element, "click", function(evnet) {
    MyApplication.handleClick(event);
})

在这段重写大代码中, MyApplication.handleClick()将x坐标和y坐标传入了MyApplication .showPopup(), 代替了之前传入的事件对象. 可以很清晰的看到MyApplication.showPopup()所期望传入的参数, 并且在测试或代码的任意位置都可以很轻易的直接调用这段逻辑, 比如:

MyApplication.showPopup(10, 10);

当处理事件时, 最好让事件处理程序成为接触到event对象的唯一的函数. 事件处理程序应当在进入应用逻辑之前针对event对象执行任何必要的操作, 包括阻止默认事件或阻止冒泡, 都应当直接包含在事件处理程序中, 比如:

// 好的做法
var MyApplication = {
    handleClick: function(event) {
    
        // 假设事件支持DOM Level2
        event.preventDefault();
        event.stopPropagation();
        
        // 传入应用逻辑
        this.showPopup(event.clientX, event.clientY);
    }
    
    showPopup: function(x, y) {
        var popup = document.getElementById("popup");
        popup.style.left = x + "px";
        popup.style.top = y + "px";
        popup.className = "reveal";
    }
};

addListener(element, "click", function(evnet) {
    MyApplication.handleClick(event);
})

在这段代码中, MyApplication.handleClick()是事件处理程序, 因此它是在将数据传入应用逻辑之前调用了event.preventDefault()和event.stopPropagation(), 这清楚的展示了事件处理程序和应用逻辑之间的分工. 因为应用逻辑不需要对event产生依赖, 进而在很多地方都可以轻松的使用相同的业务逻辑, 包括写测试代码.

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/89212.html

相关文章

  • &lt;&lt;编写维护javascript&gt;&gt; 笔记9(将配置数据从代码中分离出来

    摘要:代码无非是定义一些指令的集合让计算机来执行我们常常将数据传入计算机由指令对数据进行操作并最终产生一个结果当不得不修改数据时问题就来了任何时候你修改源代码都会有引入的风险且值修改一些数据的值也会带来一些不必要的风险因为数据时不应当影响指令的正 代码无非是定义一些指令的集合让计算机来执行. 我们常常将数据传入计算机, 由指令对数据进行操作, 并最终产生一个结果. 当不得不修改数据时问题就来...

    xbynet 评论0 收藏0
  • &lt;&lt;编写维护javascript&gt;&gt; 笔记3(语句和表达式)

    摘要:所有的块语句都应当使用花括号包括花括号的对齐方式第一种风格第二种风格块语句间隔第一种在语句名圆括号和左花括号之间没有空格间隔第二种在左圆括号之前和右圆括号之后各添加一个空格第三种在左圆括号后和右圆括号前各添加一个空格我个人喜欢在右括号之后添 所有的块语句都应当使用花括号, 包括: if for while do...while... try...catch...finally 3....

    OBKoro1 评论0 收藏0
  • &lt;&lt;编写维护javascript&gt;&gt; 笔记5(UI层松耦合)

    摘要:由于第四章太稀松平常了于是就直接跳到第五章了这里我就草草的说一下第四章的几个点吧在严格模式的应用下不推荐将用在全局作用域中相等推荐尽量使用和守则如果是在没有别的方法来完成当前任务这时可以使用原始包装类型不推荐创建类型时用等创建类型从这一章节 由于第四章太稀松平常了, 于是就直接跳到第五章了.这里我就草草的说一下第四章的几个点吧 在严格模式的应用下 不推荐将use strict;用在全...

    saucxs 评论0 收藏0
  • &lt;&lt;编写维护javascript&gt;&gt; 笔记2(注释)

    摘要:注释是代码中最常见的组成部分它们是另一种形式的文档也是程序员最后才舍得花时间去写的但是对于代码的总体可维护性而言注释是非常重要的一环打开一个没有任何注释的文件就好像趣味冒险但如果给你的时间有限这项任务就变成了折磨适度的添加注释可以解释说明代 注释是代码中最常见的组成部分.它们是另一种形式的文档,也是程序员最后才舍得花时间去写的.但是,对于代码的总体可维护性而言,注释是非常重要的一环.打...

    renweihub 评论0 收藏0
  • &lt;&lt;编写维护javascript&gt;&gt; 笔记1(基本格式化)

    摘要:程序是写给人读的只是偶尔让计算机执行一下当你刚刚组建一个团队时团队中的每个人都各自有一套编程习惯毕竟每个成员都有着不同的背景有些人可能来自某个皮包公司身兼数职在公司里面什么事都做还有些人会来自不同的团队对某种特定的做事风格情有独钟或恨之入骨 程序是写给人读的,只是偶尔让计算机执行一下. Donald Knuth 当你刚刚组建一个团队时,团队中的每个人都各自有一套编程习惯.毕竟,...

    wfc_666 评论0 收藏0

发表评论

0条评论

microelec

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<