Unobtrusive JavaScript 不唐突的JavaScript的七条准则

Unobtrusive JavaScript是一种将JavascriptHTML结构抽离的设计概念,避免在HTML标签中夹杂一堆onchange、onclick……等属性去挂载Javascript事件,让HTML与Javascript分离,依MVC的原则将功能权责清楚区分,使HTML也变得结构化容易阅读。javascript

Unobtrusive JavaScript是在网页中使用JavaScript的通常方式。该名称并不是正式定义,它的基本原则包括:java

  • 行为层和表现层分离开;
  • 是解决传统JavaScript编程问题(浏览器呈现不一致,缺少扩展性)的最佳实践;
  • 为可能不支持JavaScript高级特性的用户代理(一般是浏览器)提供渐进加强的支持

 

--------------------------------------------------------编程

 不唐突的JavaScript的七条准则

 

英文原文:The seven rules of Unobtrusive JavaScript 
原文做者:Chris Heilmann 

通过多年的开发、教学和编写不唐突的JavaScript, 我发现了下面的一些准则。我但愿它们能够帮助你对“为何这样设计和执行JavaScript比较好”有一点理解。这些规则曾经帮助我更快地交付产品,而且产品的质量更高,也更容易维护。 

1.不要作任何假设 
(JavaScript是一个不可靠的助手) 

可能不唐突的JavaScript 的最重要的一个特性就是——你要中止任何假设: 

    * 不要假设JavaScript是可用的,你最好认为它颇有多是不可用的,而不是直接依赖于它。 
    * 在你通过测试确认一些方法和属性可使用以前,不要假设浏览器支持它们。 
    * 不要假设HTML代码如你想象的那样正确,每次都要进行检查,而且当其不可用的时候就什么也不要作。 
    * 让JavaScript的功能独立于输入设备 
    * 要记住其余的脚本可能会影响你的JavaScript的功能,因此要保证你的脚本的做用域尽量地安全。 

在开始设计你的脚本以前,要考虑的第一件事情就是检查一下你要为其编写脚本的HTML代码,看看有什么东西能够帮助你达到目的。 

2.找出钩子和节点关系 
(HTML是脚本的基石) 

在开始编写脚本以前,要先看一下你要为之编写JavaScript的HTML。若是HTML是未经组织的或者未知的,那么你几乎不可能有一个好的脚本编写方案——极可能就会出现下面的状况:要么是会用JavaScript建立太多标记,要么就是应用太依赖于JavaScript。 

在HTML中有一些东西须要考虑,那就是钩子和节点关系。 

<1>.HTML 钩子 

HTML最初的和最重要的钩子就是ID,并且ID能够经过最快的DOM方法——getElementById 访问到。若是在一个有效的HTML文档中全部的ID都是独一无二的话(在IE中关于name 和 ID 有一个bug,不过有些好的类库解决了这个问题),使用ID就是安全可靠的,而且易于测试。 

其余一些钩子就是是HTML元素和CSS类,HTML元素能够经过getElementsByTagName方法访问,而在多数浏览器中都还不能经过原生的DOM方法来访问CSS类。不过,有不少外部类库提供了能够访问CSS类名(相似于 getElementsByClassName) 的方法。 

<2>.HTML 节点关系 

关于HTML的另外比较有意思的一点就是标记之间的关系,思考下面的问题: 

    * 要怎样才能够最容易地、经过最少的DOM遍从来到达目标节点? 
    * 经过修改什么标记,能够尽量多地访问到须要修改的子节点? 
    * 一个给定的元素有什么属性或信息能够用来到达另一个元素? 

遍历DOM很耗资源并且速度很慢,这就是为何要尽可能使用浏览器中已经在使用的技术来作这件事情。 

3.把遍历交给专家来作 
(CSS,更快地遍历DOM) 

有关DOM的脚本和使用方法或属性(getElementsByTagName, nextSibling, previousSibling, parentNode以及其它)来遍历DOM彷佛迷惑了不少人,这点颇有意思。而有趣的是,咱们其实早已经经过另一种技术—— CSS ——作了这些事情。 

CSS 是这样一种技术,它使用CSS选择器,经过遍历DOM来访问目标元素并改变它们的视觉属性。一段复杂的使用DOM的JavaScript能够用一个CSS选择器取代: 设计模式

Java代码 
  1. var n = document.getElementById('nav');  
  2. if(n){  
  3. var as = n.getElementsByTagName('a');  
  4. if(as.length > 0){  
  5. for(var i=0;as[i];i++){  
  6. as[i].style.color = ‘#369′;  
  7. as[i].style.textDecoration = ‘none’;  
  8. }  
  9. }  
  10. }  
  11.   
  12. /* 下面的代码与上面功能同样 */  
  13.   
  14. #nav a{  
  15. color:#369;  
  16. text-decoration:none;  
  17. }  



这是一个能够好好利用的很强大的技巧。你能够经过动态为DOM中高层的元素添加class 或者更改元素ID来实现这一点。若是你使用DOM为文档的body添加了一个CSS类,那么设计师就很能够容易地定义文档的静态版本和动态版本。 

浏览器

Java代码 
  1. JavaScript:  
  2.   
  3. var dynamicClass = 'js';  
  4. var b = document.body;  
  5. b.className = b.className ? b.className + ' js' : 'js';  
  6.   
  7. CSS:  
  8.   
  9. /* 静态版本 */  
  10.   
  11. #nav {  
  12. ....  
  13. }  
  14.   
  15. /* 动态版本 */  
  16.   
  17. body.js #nav {  
  18. ....  
  19. }  



4.理解浏览器和用户 
(在既有的使用模式上建立你所须要的东西) 

不唐突的JavaScript 中很重要的一部分就是理解浏览器是如何工做的(尤为是浏览器是如何崩溃的)以及用户指望的是什么。不考虑浏览器你也能够很容易地使用JavaScript 建立一个彻底不一样的界面。拖拽界面,折叠区域,滚动条和滑动块均可以使用JavaScript建立,可是这个问题并非个简单的技术问题,你须要思考下面的问题: 

    * 这个新界面能够独立于输入设备么?若是不能,那么能够依赖哪些东西? 
    * 我建立的这个新界面是否遵循了浏览器或者其它富界面的准则(你能够经过鼠标在多级菜单中直接切换吗?仍是须要使用tab键?) 
    * 我须要提供什么功能可是这个功能是依赖于JavaScript的? 

最后一个问题其实不是问题,由于若是须要你就可使用DOM来凭空建立HTML。关于这点的一个例子就是“打印”连接,因为浏览器没有提供一个非 JavaScript的打印文档功能,因此你须要使用DOM来建立这类连接。一样地,一个实现了展开和收缩内容模块的、能够点击的标题栏也属于这种状况。标题栏不能被键盘激活,可是连接能够。因此为了建立一个能够点击的标题栏你须要使用JavaScript将连接加入进去,而后全部使用键盘的用户就能够收缩和展开内容模块了。 

解决这类问题的极好的资源就是设计模式库。至于要知道浏览器中的哪些东西是独立于输入设备的,那就要靠经验的积累了。首先你要理解的就是事件处理机制。 

5.理解事件 
(事件处理会引发改变) 

事件处理是走向不唐突的JavaScript的第二步。重点不是让全部的东西都变得能够拖拽、能够点击或者为它们添加内联处理,而是理解事件处理是一个能够彻底分离出来的东西。咱们已经将HTML,CSS和JavaScript分离开来,可是在事件处理的分离方面却没有走得很远。 

事件处理器会监听发生在文档中元素上的变化,若是有事件发生,处理器就会找到一个很奇妙的对象(通常会是一个名为e的参数),这个对象会告诉元素发生了什么以及能够用它作什么。 

对于大多数事件处理来讲,真正有趣的是它不止发生在你想要访问的元素上,还会在DOM中较高层级的全部元素上发生(可是并非全部的事件都是这样,focus和blur事件是例外)。举例来讲,利用这个特性你能够为一个导航列表只添加一个事件处理器,而且使用事件处理器的方法来获取真正触发事件的元素。这种技术叫作事件委托,它有几点好处: 

    * 你只须要检查一个元素是否存在,而不须要检查每一个元素 
    * 你能够动态地添加或者删除子节点而并不须要删除相应的事件处理器 
    * 你能够在不一样的元素上对相同的事件作出响应 

须要记住的另外一件事是,在事件向父元素传播的时候你能够中止它并且你能够覆写掉HTML元素(好比连接)的缺省行为。不过,有时候这并非个好主意,由于浏览器赋予HTML元素那些行为是有缘由的。举个例子,连接可能会指向页面内的某个目标,不去修改它们能确保用户能够将页面当前的脚本状态也加入书签。 

6.为他人着想 
(命名空间,做用域和模式) 

你的代码几乎历来不会是文档中的惟一的脚本代码。因此保证你的代码里没有其它脚本能够覆盖的全局函数或者全局变量就显得尤其重要。有一些可用的模式能够来避免这个问题,最基础的一点就是要使用 var 关键字来初始化全部的变量。假设咱们编写了下面的脚本: 

安全

Java代码 
  1. var nav = document.getElementById('nav');  
  2. function init(){  
  3. // do stuff  
  4. }  
  5. function show(){  
  6. // do stuff  
  7. }  
  8. function reset(){  
  9. // do stuff  
  10. }  


上面的代码中包含了一个叫作nav的全局变量和名字分别为 init,show 和 reset 的三个函数。这些函数均可以访问到nav这个变量而且能够经过函数名互相访问: 

函数

Java代码 
  1. var nav = document.getElementById('nav');  
  2. function init(){  
  3. show();  
  4. if(nav.className === 'show'){  
  5. reset();  
  6. }  
  7. // do stuff  
  8. }  
  9. function show(){  
  10. var c = nav.className;  
  11. // do stuff  
  12. }  
  13. function reset(){  
  14. // do stuff  
  15. }  



你能够将代码封装到一个对象中来避免上面的那种全局式编码,这样就能够将函数变成对象中的方法,将全局变量变成对象中的属性。 你须要使用“名字+冒号”的方式来定义方法和属性,而且须要在每一个属性或方法后面加上逗号做为分割符。 

测试

Java代码 
  1. var myScript = {  
  2. nav:document.getElementById('nav'),  
  3. init:function(){  
  4. // do stuff  
  5. },  
  6. show:function(){  
  7. // do stuff  
  8. },  
  9. reset:function(){  
  10. // do stuff  
  11. }  
  12. }  



全部的方法和属性均可以经过使用“类名+点操做符”的方式从外部和内部访问到。 ui

Java代码 
  1. var myScript = {  
  2. nav:document.getElementById('nav'),  
  3. init:function(){  
  4. myScript.show();  
  5. if(myScript.nav.className === 'show'){  
  6. myScript.reset();  
  7. }  
  8. // do stuff  
  9. },  
  10. show:function(){  
  11. var c = myScript.nav.className;  
  12. // do stuff  
  13. },  
  14. reset:function(){  
  15. // do stuff  
  16. }  
  17. }  



这种模式的缺点就是,你每次从一个方法中访问其它方法或属性都必须在前面加上对象的名字,并且对象中的全部东西都是能够从外部访问的。若是你只是想要部分代码能够被文档中的其余脚本访问,能够考虑下面的模块(module)模式: 编码

Java代码 
  1. var myScript = function(){  
  2. //这些都是私有方法和属性  
  3. var nav = document.getElementById('nav');  
  4. function init(){  
  5. // do stuff  
  6. }  
  7. function show(){  
  8. // do stuff  
  9. }  
  10. function reset(){  
  11. // do stuff  
  12. }  
  13. //公有的方法和属性被使用对象语法包装在return 语句里面  
  14. return {  
  15. public:function(){  
  16.   
  17. },  
  18. foo:'bar'  
  19. }  
  20. }();  



你可使用和前面的代码一样的方式访问返回的公有的属性和方法,在本示例中能够这么访问:myScript.public() 和 myScript.foo 。可是这里还有一点让人以为不舒服:当你想要从外部或者从内部的一个私有方法中访问公有方法的时候,仍是要写一个冗长的名字(对象的名字能够很是长)。为了不这一点,你须要将它们定义为私有的而且在return语句中只返回一个别名: 

Java代码 
  1. var myScript = function(){  
  2. // 这些都是私有方法和属性  
  3. var nav = document.getElementById('nav');  
  4. function init(){  
  5. // do stuff  
  6. }  
  7. function show(){  
  8. // do stuff  
  9. // do stuff  
  10. }  
  11. function reset(){  
  12. // do stuff  
  13. }  
  14. var foo = 'bar';  
  15. function public(){  
  16.   
  17. }  



//只返回指向那些你想要访问的私有方法和属性的指针 

Java代码 
  1. return {  
  2. public:public,  
  3. foo:foo  
  4. }  
  5. }();  



这就保证了代码风格一致性,而且你可使用短一点的别名来访问其中的方法或属性。 

若是你不想对外部暴露任何的方法或属性,你能够将全部的代码封装到一个匿名方法中,并在它的定义结束后马上执行它: 

Java代码 
  1. (function(){  
  2. // these are all private methods and properties  
  3. var nav = document.getElementById('nav');  
  4. function init(){  
  5. // do stuff  
  6. show(); // 这里不须要类名前缀  
  7. }  
  8. function show(){  
  9. // do stuff  
  10. }  
  11. function reset(){  
  12. // do stuff  
  13. }  
  14. })();  



对于那些只执行一次而且对其它函数没有依赖的代码模块来讲,这种模式很是好。 

经过遵循上面的那些规则,你的代码更好地为用户工做,也可使你的代码在机器上更好地运行并与其余开发者的代码和气相处。不过,还有一个群体须要考虑到。 

7.为接手的开发者考虑 
(使维护更加容易) 

使你的脚本真正地unobtrusive的最后一步是在编写完代码以后仔细检查一遍,而且要照顾到一旦脚本上线以后要接手你的代码的开发者。考虑下面的问题: 

    * 全部的变量和函数名字是否合理而且易于理解? 
    * 代码是否通过了合理的组织?从头至尾都很流畅吗? 
    * 全部的依赖都显而易见吗? 
    * 在那些可能引发混淆的地方都添加了注释吗? 

最重要的一点是:要认识到文档中的HTML和CSS代码相对于JavaScript来讲更有可能被改变(由于它们负责视觉效果)。因此不要在脚本代码中包含任何可让终端用户看到的class和ID,而是要将它们分离出来放到一个保存配置信息的对象中。 

Java代码 
  1. myscript = function(){  
  2. var config = {  
  3. navigationID:'nav',  
  4. visibleClass:'show'  
  5. };  
  6. var nav = document.getElementById(config.navigationID);  
  7. function init(){  
  8. show();  
  9. if(nav.className === config.visibleClass){  
  10. reset();  
  11. };  
  12. // do stuff  
  13. };  
  14. function show(){  
  15. var c = nav.className;  
  16. // do stuff  
  17. };  
  18. function reset(){  
  19. // do stuff  
  20. };  
  21. }();  



这样维护者就知道去哪里修改这些属性,而不须要改动其余代码。 


更多信息 

以上就是我发现的七条准则。若是你想要了解更多与上面所探讨的主题相关的东西,能够看看下面的连接: 

    * Yahoo! Design Pattern Library 
    * Event Delegation 
    * Event Driven JavaScript Application Design 
    * JavaScript Programming Patterns 
    * Show love to the Object Literal 
    * A JavaScript Module Pattern 

相关文章
相关标签/搜索