做者 Christian Heilmann · 2007年12月19日javascript
本文翻译自 The seven rules of Unobtrusive JavaScriptjava
在开发、教学和实现 Unobtrusive JavaScript 的工做中,我总结了下面七条规则。本文实际是关于 Unobtrusive javascript 的一次会议上(Paris Web conference 2007 ,巴黎)的发言稿提纲。设计模式
我但愿本文能帮你理解为何要如此编写 JavaScript 代码。它曾帮助我更快的交付产品、提升产品质量并减轻维护工做量。浏览器
Unobtrusive JavaScript 最重要的规则就是中止作任何假设:安全
不要假设 JavaScript 必定可使用,不该依赖 JavaScript 而应当把它看成助手。app
不要假设浏览器支持某些方法及具备某些属性,在使用前测试是否可用ide
不要假设 HTML 代码是正确的,使用前最好先检查器正确性函数
保持功能独立于输入设备测试
你要考虑到其余脚本可能会影响你的代码,应让你的代码做用域尽量安全spa
在开始编写脚边以前需作的第一件事就是查看要用脚本加强的HTML代码,并想一想有哪些能够加以利用。
在开始编写脚本以前先看看 HTML 文档。若是 HTML 结构混乱,很难为之建立漂亮的脚本方案——要么你不得不用 JavaScript 建立大量的标记;要么你的网页要依靠 JavaScript 。.
HTML 代码中须要考虑下面问题:钩子和关系。
最重要的 HTML 钩子是 ID,由于可经过最快的 DOM 方法 getElementById
获取标记元素。合法的 HTML 文档中 ID 是惟一的 ( IE 中有一个关于 name 和 ID 的 bug,但好的库都能绕开此 bug ),因此在其中使用 ID 和安全的。
其余钩子是可以使用 getElementsByTagName
获取的 HTML 元素和 CSS class——大部分浏览器中没有原生 DOM 方法可用于获取 CSS class ( Mozilla 之后将有,Opera 9.5 已经包含);但有不少库提供 getElementsByClassName
以访问 CSS class。
HTML 另外一个颇有趣的地方时标记间的关系。请问问本身下面问题:
如何最快最简单的遍历 DOM 并得到所需元素?
当须要改变多个子元素时,应该改变哪一个元素?
元素的哪些属性或信息能够帮助访问其余元素?
遍历 DOM 树是较慢的操做,这就是为何最好采用已在浏览器中被使用的技术。
DOM 提供了遍历 DOM 的方法和属性 ( getElementsByTagName
, nextSibling
, previousSibling
, parentNode
等),这些属性经常让人迷惑。有趣的是咱们已有达到此目的的技术: CSS。
CSS 经过 CSS 选择器遍历 DOM 以访问所需的元素并改变其视觉属性。可用简单的 CSS 选择器取代复杂的 JavaScript DOM 函数:
var n = document.getElementById('nav'); if(n){ var as = n.getElementsByTagName('a'); if(as.length > 0){ for(var i=0;as[i];i++){ as[i].style.color = '#369'; as[i].style.textDecoration = 'none'; } } } /* is the same as */ #nav a{ color:#369; text-decoration:none }
CSS 是强大的能够加以利用的技术。能够动态地给高层的 DOM 元素添加 class,或者修改 ID 。如使用 DOM 给 body 添加 class,那网页设计者能够轻松地定义网页的静态和动态版本:
JavaScript:var dynamicClass = 'js'; var b = document.body; b.className = b.className ? b.className + ' js' : 'js';CSS:/* static version */ #nav { .... } /* dynamic version */ body.js #nav { .... }
Unobtrusive JavaScript 的一个重要部分就是理解浏览器如何工做 (特别理解为什么浏览器会不能正常工做),并理解用户指望是什么。开发者很容易轻率的使用 JavaScript 建立彻底不一样的界面:可拖拽界面、可折叠区域、滚动条,这些均可以经过 JavaScript 实现,但这不只仅是技术问题。你应该问问你本身:
个人新用户界面独立于输入设备么?若是答案是否,那备用方案是什么?
新界面是否符合浏览器规则或富界面( richer interface)规则?(你能使用光标在多级菜单中导航么?仍是须要使用 tab ?)
什么功能是须要提供的,但又依赖于 JavaScript 的?
最后一项不是问题,在须要时可使用 DOM 建立 HTML 。一个很好的例子就是"打印此页"连接 —— 浏览器没有提供非 JavaScript 方式打印网页,因此你须要使用DOM建立此类连接。一样的,可点击并可展开/收缩其内容的标题也须要使用 DOM 建立。没法使用键盘激活标题,但能够激活连接。为了建立可点击的标题,你应该使用JavaScript为其添加连接 —— 这样即便是键盘用户也能够展开/收缩内容。
解决此类问题的很好的资源就是设计模式库。知道浏览器中什么特性独立与输入设备是一个经验问题。首先须要理解事件处理概念。
事件处理是 Unobtrusive JavaScript 的重要一环。重点不是让全部元素均可以点击并拖拽或添加内联处理;重点是理解事件处理是真正的分离。咱们分离了 HTML, CSS 和 JavaScript ,但有了事件处理可更进一步。
网页中的元素等待处理器监听状态变化。变化发生后,处理器得到“神奇的”对象 (一般是名为e
的参数),此对象会告知哪些元素发生了哪些事件,及如何应变。
事件处理很酷的一点是事件不只发生在一个元素中,事件会达到 DOM 树中全部的祖先元素 (全部事件都有此特性,但 focus 和 blur 除外)。这容许你为多个元素设置同一个事件处理器(如为导航列表),使用事件处理器方法便可获取真正触发事件的元素。此技巧被称做事件委托( event delegation ),其优势以下:
你只需测试一个元素是否存在,没必要测试全部元素
能够动态添加或删除子元素,而不须要移除或添加相应新事件处理器
能够响应不一样元素的同一个事件
另外能够中止事件向父节点传播,也能够重载 HTML 元素(如连接)的默认行为。但有时这不是一个好主意,浏览器这么作是有缘由的。好比,指向页内目标的连接,容许它们被跟随能够确保用户可将脚本状态也加入书签。
你的代码不可能只用于这一个网页。所以须要确保你的代码中没有全局函数且没有变量名称不会和其余脚本冲突。有不少方案能够保证这一点。最简单的方法就是为每个变量初始化使用var
关键字。假设有如下代码:
var nav = document.getElementById('nav'); function init(){ // do stuff } function show(){ // do stuff } function reset(){ // do stuff }
上面代码有一个全局变量 nav
和三个全局函数 init
, show
及 reset
。这三个函数均可以使用 nav 变量及另两个函数:
var nav = document.getElementById('nav'); function init(){ show(); if(nav.className === 'show'){ reset(); } // do stuff } function show(){ var c = nav.className; // do stuff } function reset(){ // do stuff }
可经过将上面代码封装到对象中避免污染全局命名空间,这样函数就变成了对象方法,变量变成了对象属性。在变量名称或方法名称后使用冒号,并在定义之间使用逗号分隔。
var myScript = { nav:documentgetElementById('nav'), init:function(){ // do stuff }, show:function(){ // do stuff }, reset:function(){ // do stuff } }
在对象内外均可以经过使用对象名加'.'来使用对象属性或者对象方法。
var myScript = { nav:documentgetElementById('nav'), init:function){ myScript.show(); if(myScript.nav.className === 'show'){ myScript.reset(); } // do stuff }, show:function){ var c = myScript.nav.className; // do stuff }, reset:function){ // do stuff } }
这种方法的缺点是每当须要使用变量或方法时都须要重复输入对象名,且对象中全部变量和方法都是能够公开访问的。若是你想让部分脚本成为公开访问的呢?可使用模块模式:
var myScript = function(){ // these are all private methods and properties var nav = document.getElementById('nav'); function init(){ // do stuff } function show(){ // do stuff } function reset(){ // do stuff } // public methods and properties wrapped in a return // statement and using the object literal return { public:function(){ }, foo:'bar' } }();
能够公开访问的属性和方法被封住进 return 语句。上例中 myScript.public()
和 myScript.foo
能够在外部使用。烦人的是若是你想在另外一个公开方法或者私有方法中使用某个公开方法,你必需要使用很长的名称 (主对象名可能会很长)。为克服此缺点,将其定义为私有方法并返回别名:
var myScript = function(){ // these are all private methods and properties var nav = document.getElementById('nav'); function init(){ // do stuff } function show(){ // do stuff // do stuff } function reset(){ // do stuff } var foo = 'bar'; function public(){ } // return public pointers to the private methods and // properties you want to reveal return { public: public, foo:foo } }();
这保持了代码风格的一致性并可以使用短一些的名称访问。
若是你不想让外面代码调用函数,可将代码封装在匿名函数中并在定义后当即使用:
(function(){ // these are all private methods and properties var nav = document.getElementById('nav'); function init(){ // do stuff show(); // no need for prepended object name } function show(){ // do stuff } function reset(){ // do stuff } })();
这对那些只执行一遍且不依赖其余函数的函数来讲是一个很好的模式。
遵循上述规则可让你的代码更好的为用户服务、并更好的在机器中与其余人的代码相互配合工做。但此外你还须要考虑另外一群人。
编写Unobtrusive JavaScript的最后一个环节是在开发结束后从新审视代码,并为脚本上线后接手此代码的下一位开发者着想:
每个变量名和函数名都符合逻辑且易懂么?
代码结构如何?你能顺畅的从头读到尾么?
全部的依赖性都显而易见么?
在那些容易引发疑惑的地方你添加注释了么?
须要牢记的是 HTML 和 CSS 相比JavaScript来讲更可能被改变 (由于他们构成了视觉输出)。所以不要让 class 、 ID 名称及要显示给用户的字符串被深深“埋藏”在你的代码中,最好将其统一放置在 config 对象中。
myscript = function(){ var config = { navigationID:'nav', visibleClass:'show' }; var nav = document.getElementById(config.navigationID); function init(){ show(); if(nav.className === config.visibleClass){ reset(); }; // do stuff }; function show(){ var c = nav.className; // do stuff }; function reset(){ // do stuff }; }();
这样代码维护人员可知道去何处修改代码,且没必要改动其余代码。