JavaScript触发器详解_javascript技巧

一个网站的前端由三个层构成。由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文档中。

时间: 2024-11-05 21:55:13

JavaScript触发器详解_javascript技巧的相关文章

JS高级调试技巧:捕获和分析 JavaScript Error详解_javascript技巧

反正只要 JavaScript 出错后刷新不复现,那用户就可以通过刷新解决问题,浏览器不会崩溃,当没有发生过好了.这种假设在 Single Page App 流行之前还是成立的.现在的 Single Page App 运行一段时间后状态复杂无比,用户可能进行了若干输入操作才来到这里的,说刷新就刷新啊?之前的操作岂不要完全重做?所以我们还是有必要捕获和分析这些异常信息的,然后我们就可以修改代码避免影响用户体验. 捕获异常的方式 我们自己写的 throw new Error() 想要捕获当然可以捕获

在Node.js中使用Javascript Generators详解_javascript技巧

Generators是Javascript的一种协同程序( coroutine 简称:协程)风格,是指那些可以在执行时暂停然后又恢复的函数,该函数是在functi配以星号符号形式如function* ,函数内有些特征关键词如yield 和yield*. function* generatorFn () { console.log('look ma I was suspended') } var generator = generatorFn() // [1] setTimeout(functio

JavaScript构造函数详解_javascript技巧

构造函数就是初始化一个实例对象,对象的prototype属性是继承一个实例对象.   构造函数注意事项: 1.默认函数首字母大写 2.构造函数并没有显示返回任何东西.new 操作符会自动创建给定的类型并返回他们,当调用构造函数时,new会自动创建this对象,且类型就是构造函数类型. 3.也可以在构造函数中显示调用return.如果返回的值是一个对象,它会代替新创建的对象实例返回.如果返回的值是一个原始类型,它会被忽略,新创建的实例会被返回.     function Person( name)

javascript 闭包详解_javascript技巧

看了一下网上闭包的概念及文章,对于这个问题,自己做一个梳理吧. 问:闭包是什么? 答:闭包是指在 JavaScript 中,内部函数总是可以访问其所在的外部函数中声明的参数和变量,即使在其外部函数被返回(寿命终结)了之后. 这个是我自身第一次碰到闭包的问题 <!DOCTYPE HTML> <html> <head> <meta charset="utf-8"/> <title>闭包循环问题</title> <

javascript控制台详解_javascript技巧

一.显示信息的命令 console.log(); //控制台输入 网页中不会输出 console.info(); //一般信息 console.debug(); //除错信息 console.warn(); //警告提示 console.error(); //错误提示 "console.log();" 可以用来取代 "alert();" 或 "document.write();" 比如,在网页中写入 "console.log("

JavaScript多线程详解_javascript技巧

虽然有越来越多的网站在应用AJAX技术进行开发,但是构建一个复杂的AJAX应用仍然是一个难题. 造成这些困难的主要原因是什么呢?是与服务器的异步通信问题?还是GUI程序设计问题呢?通常这两项工作都是由桌面程序来完成的,那究竟为何开发一个可以实现同样功能的AJAX应用就这么困难呢? 大家都知道javascript是单线程执行的,但是又可以通过setTimeout或者setInterval定时执行一个方法,通过Ajax可以在向服务器端发送请求没有收到回应可以继续执行主逻辑.这些是如何做到的呢,下面就

JavaScript 异常处理 详解_javascript技巧

前端工程师都知道 JavaScript 有基本的异常处理能力.我们可以 throw new Error(),浏览器也会在我们调用 API 出错时抛出异常.但估计绝大多数前端工程师都没考虑过收集这些异常信息 反正只要 JavaScript 出错后刷新不复现,那用户就可以通过刷新解决问题,浏览器不会崩溃,当没有发生过好了.这种假设在 Single Page App 流行之前还是成立的.现在的 Single Page App 运行一段时间后状态复杂无比,用户可能进行了若干输入操作才来到这里的,说刷新就

JavaScript 数组详解_javascript技巧

在程序语言中数组的重要性不言而喻,JavaScript中数组也是最常使用的对象之一,数组是值的有序集合,由于弱类型的原因,JavaScript中数组十分灵活.强大,不像是Java等强类型高级语言数组只能存放同一类型或其子类型元素,JavaScript在同一个数组中可以存放多种类型的元素,而且是长度也是可以动态调整的,可以随着数据增加或减少自动对数组长度做更改. 创建数组 在JavaScript多种方式创建数组 构造函数 1.无参构造函数,创建一空数组 复制代码 代码如下: var a1=new

JavaScript函数详解_javascript技巧

1.函数定义  函数包含一组语句,它们是javascript的基础模块单元,用于代码复用.信息隐藏和组合调用.函数用于指定对象的行为 2.函数的四种调用模式及this的初始化 第一种:方法调用模式     以下事例证明通过方法调用模式调用时,this绑定到拥有该方法的对象.如: 复制代码 代码如下: var person = {     name: "defaultName",     setName : function(name){         this.name = name