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 相关文章推荐
动态样式类封装JS代码
Sep 02 Javascript
禁止JQuery中的load方法装载IE缓存中文件的方法
Sep 11 Javascript
Javascript 面向对象编程(一) 封装
Aug 28 Javascript
防止浏览器记住用户名及密码的简单实用方法
Apr 22 Javascript
jquery获取复选框被选中的值
Mar 22 Javascript
jQuery获取this当前对象子元素对象的方法
Nov 29 Javascript
基于Three.js插件制作360度全景图
Nov 29 Javascript
div实现自适应高度的textarea实现angular双向绑定
Jan 08 Javascript
AngularJS实现tab选项卡的方法详解
Jul 05 Javascript
jQuery实现轮播图效果
Nov 26 jQuery
Vue的transition-group与Virtual Dom Diff算法的使用
Dec 09 Javascript
Element InputNumber计数器的使用方法
Jul 27 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
第十一节 重载 [11]
2006/10/09 PHP
浅析php插件 Simple HTML DOM 用DOM方式处理HTML
2013/07/01 PHP
php好代码风格的阶段性总结
2016/06/25 PHP
php简单构造json多维数组的方法示例
2017/06/08 PHP
许愿墙中用到的函数
2006/10/07 Javascript
IE6不能修改NAME问题的解决方法
2010/09/03 Javascript
如何学习Javascript入门指导
2013/11/01 Javascript
JQuery拖动表头边框线调整表格列宽效果代码
2014/09/10 Javascript
使用JavaScript+canvas实现图片裁剪
2015/01/30 Javascript
javascript实现的图片切割多块效果实例
2015/05/07 Javascript
jQuery解析XML文件同时动态增加js文件的方法
2015/06/01 Javascript
JavaScript代码轻松实现网页内容禁止复制(代码简单)
2015/10/23 Javascript
JS实现登录页面记住密码和enter键登录方法推荐
2016/05/10 Javascript
AngularJS实现数据列表的增加、删除和上移下移等功能实例
2016/09/05 Javascript
jQuery在header中设置请求信息的方法
2017/03/06 Javascript
微信小程序wx.previewImage预览图片实例详解
2017/12/07 Javascript
Node使用Sequlize连接Mysql报错:Access denied for user ‘xxx’@‘localhost’
2018/01/03 Javascript
什么时候不能在 Node.js 中使用 Lock Files
2019/06/24 Javascript
nuxt.js 在middleware(中间件)中实现路由鉴权操作
2020/11/06 Javascript
Python开发编码规范
2006/09/08 Python
python获取指定目录下所有文件名列表的方法
2015/05/20 Python
python 中random模块的常用方法总结
2017/07/08 Python
pytorch中tensor的合并与截取方法
2018/07/26 Python
python利用selenium进行浏览器爬虫
2019/04/25 Python
手把手教你用纯css3实现轮播图效果实例
2017/05/04 HTML / CSS
Stella McCartney官网:成衣、包袋、香水、内衣、童装及Adidas系列
2018/12/20 全球购物
类成员函数的重载、覆盖和隐藏区别
2016/01/27 面试题
如何用Java实现列出某个目录下的所有子目录
2015/07/20 面试题
教育学习自我评价
2014/02/03 职场文书
房产委托公证书
2014/04/08 职场文书
领导班子个人对照检查材料(群众路线)
2014/09/26 职场文书
营销经理工作检讨书
2014/11/03 职场文书
会计岗位职责范本
2015/04/02 职场文书
暑期辅导班宣传单
2015/07/14 职场文书
开业典礼致辞
2015/07/29 职场文书
执行力心得体会范文
2016/01/11 职场文书