JavaScript触发器详解


Posted in Javascript onMarch 10, 2007

一个网站的前端由三个层构成。由XHTML构建的结构层,它包括结构化和有语义的标签,以及网站的内容。可以在这一层之上增加一个表现层(CSS)和一个行为层(JavaScript),它们使网站看起来更漂亮,对用户更友好。这三层之间应该保持严格的分离。打个比方来说,应该具有这样的可能性:可以重写整个表现层而完全不需要触动到结构层和行为层。 

除了这种严格的分离,表现层和行为层都需要得到来自结构层的指令。它们必须知道在哪里应用样式,在什么时候初始化行为——换句话说:它们需要触发器。

CSS的触发器大家都很了解。class和id属性使你可以完全地控制网站的表现。然而,通过使用内联的样式属性(译者注:指写在XHTML标签中的style="..."属性),你也可以在不使用这些触发器的情况下工作,但这种用法是应该被强烈反对的。当你想要重新定义网站表现的时候,就会被迫连XHTML结构层也一起改掉。它们的出现破坏了表现和结构之间的分离。

JavaScript触发器

行为层也应该可以用同样的方式工作。通过抛弃使用内联的事件句柄(比如onmouseover="switchImages('fearful',6,false)"),我们可以把行为和结构分开。和CSS一样,我们应该使用触发器去告诉脚本在哪里部署行为。

最简单的JavaScript触发器是id属性。

<div id="navigation"> <ul>  <li><a href="#">Link 1</a></li>  <li><a href="#">Link 2</a></li>  <li><a href="#">Link 3</a></li> </ul></div>var x = document.getElementById('navigation');if (!x) return;var y = x.getElementsByTagName('a');for (var i=0;i<y.length;i++) y[i].onmouseover = addBehavior;

这样一来,这段脚本就由是否出现id="navigation"来触发了。如果没有id="navigation",那么什么也不会发生(if (!x) return);如果它出现了,那么所有被它包围的链接元素(指a标签)都会得到一个mouseover行为。这种解决方案简洁、优雅,在所有的浏览器中都能工作。如果这种方法已经能够满足你的需求,那么你就不需要再读下去了:)

高级触发器

不幸的是有些情况下你不能使用id作为触发器:

一个id只能在文档中出现一次,有时候你可能想把同样的行为加到几个(或一组)元素之上。 
有些情况下脚本需要比仅仅指出“在这里部署”更多的信息(译者注:比如传递一些参数)。 

我们用表单脚本来作上面两个问题的例子。给XHTML加上表单校验触发器会很实用,比如指定“这个输入域(译者注:文字输入框、密码输入框等)是必填的”。为了实现这样的触发器,我们很可能得到如下的脚本:

function validateForm(){ var x = document.forms[0].elements; for (var i=0;i<x.length;i++) {  if ([这个输入域是必填的] && !x[i].value)    // notify user of error }}

我们需要创建怎样的触发器才能告诉这个脚本哪些输入域是必填的呢?显然用id是不行的:理想的解决方案应该可以对一大堆输入域起作用。很自然的我们会想到是否可以用class来触发行为:

<input name="name" class="required" />if (x[i].className == 'required' && !x[i].value)  //提示用户输入此域

然而严格地说,class属性是用于定义CSS触发器的。把CSS和JavaScript的触发器合起来定义并不是不可能,但这样做很可能使代码变得一片混乱:

<input name="name" class="largefield required" />if (  x[i].className.indexOf('required') != -1 &&  !x[i].value)

依我看来,class属性应该只能用于CSS。class是XHTML表现层的主要触发器,如果再让它承载行为层的信息就会使问题变得复杂化。用class属性同时触发两个层是与行为和表现分离相抵触的,但到底怎么做还是应该由你自己视情况而定。

信息传递触发器

此外,触发器也可以变得更复杂一些,而不仅仅是一个声明“在这里部署(行为)”的命令。有时候你可能想给触发器加一个变量,这样可以使行为层变得更加通用,可以对每一个XHTML元素个体的需求作出响应,而不是傻乎乎地执行一个标准的脚本。

拿一个表单打比方,这个表单包括一些字符串长度有上限的文本输入框。原先的maxlength属性已经不再在textarea元素上工作,所以我们必须写个脚本来做这件事。另外,并不是这个表单里所有的文本输入框都有相同的字符串长度上限,所以在某个地方把它们个自的上限长度存起来就显得很必要了。

我们希望有这样一个东西:

var x = document.getElementsByTagName('textarea');for (var i=0;i<x.length;i++){ if ([这个文本输入框有长度上限])  x[i].onkeypress = checkLength;}function checkLength(){ var max = [读取长度上限的值]; if (this.value.length > max)  // notify user of error}

这段脚本需要两个关键的信息:

这个文本输入框有长度上限吗?这是一个很概括的触发器,告诉脚本某些行为应该加在这里。 
上限是什么?这是一个值,使得脚本可以正确地检查用户输入。 

在这里,用基于class的方式不再合适了。虽然从技术上讲不是不能做到,但是所需的代码会变得太复杂。打个比方,我们来给一个本身就带有一个叫“large”的class的文本输入框加上触发器,以便告诉脚本它是必填的,而且长度上限是300:

<textarea class="large required maxlength=300"></textarea>

这样做不光把表现层和行为层混合在了一起,而且用于读出这个值的脚本也会变得比较怪异:

var max = this.className.substring(  this.className.indexOf('maxlength')+10);if (this.value.length > max) // 提醒用户出错了。

很容易就注意到这段脚本只有当我们把maxlength=x放在最后一个的时候才能工作。如果我们想让这个脚本可以处理不是放在最后一个的maxlength=x(这种情况是常有的,比如我们想再加一个传值的触发器),它会变得更加复杂。

面临的问题

这就是我们现在面临的问题。如何才能添加完美的JavaScript触发器,让我们可以方便地把一般声明(“在这里部署行为”)和针对元素的值一起传给脚本?

从技术上来说,把这些信息一起加到class属性上是可能的,但问题是class是被设计出来做这件事的吗?

自定义属性

我转向另一种解决方案。再来看一下前面提到的textarea长度限制的例子。我们需要两部分信息:

这个文本输入框有长度上限吗? 
上限是什么? 

用自然、有语义的方式来表达这些信息需要添加一个自定义属性给textarea:

<textarea class="large" maxlength="300"></textarea>

maxlength属性通知脚本检查用户输入,并通过属性的值把长度上限传给脚本。同理,我们可以把“required”触发器也改为一个自定义属性。比如:required="true",无论给它赋什么值都可以,因为它只是起一个通知的作用,无须附带任何额外的信息。

<textarea class="large" maxlength="300" required="true"></textarea>

从技术上说,这么做没有任何问题。W3C DOM的 getAttribute()方法可以从任何一个标签中读取任意属性的值。只有7.54版以前的Opera不允许从标签(如 <h2>)中读取一个错误的属性(如src)。幸运的是之后的版本对 getAttribute()提供了完全的支持。

下面就是我的解决方案:

function validateForm(){ var x = document.forms[0].elements; for (var i=0;i<x.length;i++) {  if (x[i].getAttribute('required') && !x[i].value)    // notify user of error }}var x = document.getElementsByTagName('textarea');for (var i=0;i<x.length;i++){ if (x[i].getAttribute('maxlength'))  x[i].onkeypress = checkLength;}function checkLength(){ var max = this.getAttribute('maxlength'); if (this.value.length > max)  // notify user of error}

依我看来,这种方案很容易实现,而且与JavaScript触发器应有的形式一致:一对“变量名/值”提供了触发器的名字和脚本所需的值,它同时允许你针对每一个元素个体定义行为。最后,这种向XHTML添加触发器的方式对于初学者来说也是相当简单的。

自定义DTD

但这里又出现了另一个问题,用这种方案制作的页面无法通过校验。检验器认为required 和 maxlength非法的。检验器这样做当然是完全正确的,XHTML当中压根就没有前一个属性,后一个属性也只属于 <input>元素。

解决方案就是让它们合法:定义一个自定义的文档类型定义(Document TYpe Definition,DTD),把XHTML作一个小小的扩展,使它包含我们定义的属性。这个自定义的DTD定义了新增的属性以前它们应该出现的正确位置,然后校验器就会按我们自定义的XHTML口味来校验文档。如果DTD说这些属性是正确的,那它们就是正确的。

如果你不了解如何创建一个的DTD,请阅读这篇J.David Eisenberg发表的《创建自定义的DTD》(译者注:看大家的意见再决定是否翻译^_^),在这篇文章中他会告诉你所有你想知道的。

依我看来,用自定义属性来触发行为层——配合使这些属性合法的自定义的DTD  ——可以帮助我们把行为层与结构层分离,同时保持简洁的代码和高效的脚本。此外,一旦把这些属性和脚本定义好,即使最菜的菜鸟也可以方便地把触发器加入到XHTML文档中。

Javascript 相关文章推荐
JQuery设置和去除disabled属性的5种方法总结
May 16 Javascript
jQuery选择器全集详解
Nov 24 Javascript
微信小程序 Audio API详解及实例代码
Sep 30 Javascript
JavaScript构建自己的对象示例
Nov 29 Javascript
angular2系列之路由转场动画的示例代码
Nov 09 Javascript
关于vue中watch检测到不到对象属性的变化的解决方法
Feb 08 Javascript
浅谈webpack 自动刷新与解析
Apr 09 Javascript
vue 解决循环引用组件报错的问题
Sep 06 Javascript
解决layui的table插件无法多层级获取json数据的问题
Sep 19 Javascript
JS document form表单元素操作完整示例
Jan 13 Javascript
VUE UPLOAD 通过ACTION返回上传结果操作
Sep 07 Javascript
vue组件添加事件@click.native操作
Oct 30 Javascript
又一个图片自动缩小的JS代码
Mar 10 #Javascript
基础的prototype.js常用函数及其用法
Mar 10 #Javascript
优秀js开源框架-jQuery使用手册(1)
Mar 10 #Javascript
用JavaScript实现仿Windows关机效果
Mar 10 #Javascript
Javascript中的Split使用方法与技巧
Mar 09 #Javascript
用JavaScript事件串连执行多个处理过程的方法
Mar 09 #Javascript
一个不错的用JavaScript实现的UBB编码函数
Mar 09 #Javascript
You might like
PHP 和 HTML
2006/10/09 PHP
在PHP中使用XML
2006/10/09 PHP
有关 PHP 和 MySQL 时区的一点总结
2008/03/26 PHP
PHP正则匹配反斜杠'\'和美元'$'的方法
2017/02/08 PHP
thinkphp整合系列之极验滑动验证码geetest功能
2019/06/18 PHP
web基于浏览器的本地存储方法应用
2012/11/27 Javascript
JavaScript动态操作表格实例(添加,删除行,列及单元格)
2013/11/25 Javascript
标题过长使用javascript按字节截取字符串
2014/04/24 Javascript
js动态移动滚动条至底部示例代码
2014/04/24 Javascript
JavaScript操作XML文件之XML读取方法
2015/06/09 Javascript
JavaScript为事件句柄绑定监听函数实例详解
2015/12/15 Javascript
浅谈在fetch方法中添加header后遇到的预检请求问题
2017/08/31 Javascript
JQuery选中select组件被选中的值方法
2018/03/08 jQuery
vue移动端UI框架实现QQ侧边菜单组件
2018/03/09 Javascript
React router动态加载组件之适配器模式的应用详解
2018/09/12 Javascript
js+html实现周岁年龄计算器
2019/06/25 Javascript
javascript实现数字时钟效果
2021/02/06 Javascript
python类定义的讲解
2013/11/01 Python
Python计时相关操作详解【time,datetime】
2017/05/26 Python
Python数据分析之获取双色球历史信息的方法示例
2018/02/03 Python
python flask安装和命令详解
2019/04/02 Python
python实现列表的排序方法分享
2019/07/01 Python
python re.sub()替换正则的匹配内容方法
2019/07/22 Python
Numpy 中的矩阵求逆实例
2019/08/26 Python
Tensorflow矩阵运算实例(矩阵相乘,点乘,行/列累加)
2020/02/05 Python
英国音乐设备和乐器商店:Gear4music
2017/10/16 全球购物
美国中西部家用医疗设备商店:Med Mart(轮椅、踏板车、升降机等)
2019/04/26 全球购物
法国床上用品商店:La Compagnie du lit
2019/12/26 全球购物
维德科技C#面试题笔试题
2015/12/09 面试题
车间班长岗位职责
2013/11/30 职场文书
库房主管岗位职责
2013/12/31 职场文书
留学自荐信写作方法
2014/01/27 职场文书
美术社团活动总结
2014/06/27 职场文书
升国旗演讲稿
2014/09/05 职场文书
小学元宵节活动总结
2015/02/06 职场文书
经典《舰娘》游改全新动画预告 预定11月开播
2022/04/01 日漫