阅读更多

17顶
3踩

Web前端

转载新闻 不唐突的JavaScript的七条准则

2008-10-10 13:24 by 资深编辑 wutao0603 评论(15) 有15264人浏览
英文原文: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选择器取代:
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’;
}
}
}

/* 下面的代码与上面功能一样 */

#nav a{
color:#369;
text-decoration:none;
}


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

JavaScript:

var dynamicClass = 'js';
var b = document.body;
b.className = b.className ? b.className + ' js' : 'js';

CSS:

/* 静态版本 */

#nav {
....
}

/* 动态版本 */

body.js #nav {
....
}


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

不唐突的JavaScript 中很重要的一部分就是理解浏览器是如何工作的(尤其是浏览器是如何崩溃的)以及用户期望的是什么。不考虑浏览器你也可以很容易地使用JavaScript 创建一个完全不同的界面。拖拽界面,折叠区域,滚动条和滑动块都可以使用JavaScript创建,但是这个问题并不是个简单的技术问题,你需要思考下面的问题:

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

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

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

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

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

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

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

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

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

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

你的代码几乎从来不会是文档中的唯一的脚本代码。所以保证你的代码里没有其它脚本可以覆盖的全局函数或者全局变量就显得尤为重要。有一些可用的模式可以来避免这个问题,最基础的一点就是要使用 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:document.getElementById('nav'),
init:function(){
// do stuff
},
show:function(){
// do stuff
},
reset:function(){
// do stuff
}
}


所有的方法和属性都可以通过使用“类名+点操作符”的方式从外部和内部访问到。
var myScript = {
nav:document.getElementById('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
}
}


这种模式的缺点就是,你每次从一个方法中访问其它方法或属性都必须在前面加上对象的名字,而且对象中的所有东西都是可以从外部访问的。如果你只是想要部分代码可以被文档中的其他脚本访问,可以考虑下面的模块(module)模式:
var myScript = function(){
//这些都是私有方法和属性
var nav = document.getElementById('nav');
function init(){
// do stuff
}
function show(){
// do stuff
}
function reset(){
// do stuff
}
//公有的方法和属性被使用对象语法包装在return 语句里面
return {
public:function(){

},
foo:'bar'
}
}();


你可以使用和前面的代码同样的方式访问返回的公有的属性和方法,在本示例中可以这么访问:myScript.public() 和 myScript.foo 。但是这里还有一点让人觉得不舒服:当你想要从外部或者从内部的一个私有方法中访问公有方法的时候,还是要写一个冗长的名字(对象的名字可以非常长)。为了避免这一点,你需要将它们定义为私有的并且在return语句中只返回一个别名:
var myScript = function(){
// 这些都是私有方法和属性
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:public,
foo:foo
}
}();


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

如果你不想对外部暴露任何的方法或属性,你可以将所有的代码封装到一个匿名方法中,并在它的定义结束后立刻执行它:

(function(){
// these are all private methods and properties
var nav = document.getElementById('nav');
function init(){
// do stuff
show(); // 这里不需要类名前缀
}
function show(){
// do stuff
}
function reset(){
// do stuff
}
})();


对于那些只执行一次并且对其它函数没有依赖的代码模块来说,这种模式非常好。

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

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

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

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

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

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
};
}();


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


更多信息

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

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

来自: zhuoqun.net
17
3
评论 共 15 条 请登录后发表评论
15 楼 wangdun686 2010-11-14 21:15
嗯,受益匪浅呵呵,谢谢
14 楼 Elvis_Wu 2010-10-30 15:31
我认为,翻译为《和谐的javascript》更合适
13 楼 hjqcan 2010-02-25 12:48
[flash=200,200][/flash]大法官
引用
12 楼 lianjie1229 2009-10-20 13:33
dtyghnjdgyhdfg
引用
11 楼 tianjie1987 2009-09-09 14:27
    
10 楼 Echelon 2009-09-02 22:05
9 楼 xiao2009 2009-08-07 14:29
文章写得不错,刚刚大量使用js,我相信这是对我会有很大帮助!
8 楼 xiaoming686 2009-05-11 14:22
   
7 楼 walkman 2008-11-19 17:31
“不唐突的JavaScript“,这个翻译太叫人唐突了,即使我也不知道如何翻译更恰当
6 楼 chengj 2008-10-14 14:02
深有同感,所以我倾向于在一个比较强大的js库的基础上使用,比如jquery,这样或许更好一些,各位以为如何?
5 楼 咖啡刀 2008-10-13 14:00
文章不错!js是个很好的东西!很爱很爱他!

并非上面大家留言的那么恐怖!正如佛家有云:魔由心生,心生万象。

4 楼 BaSaRa 2008-10-11 20:38
写得很有道理,用了这么久JS,深有感触
对作用域的理解很有道理,不过我一般都不这样写 
3 楼 allenny 2008-10-11 17:18
看完此篇,我不用JS了
2 楼 fornever 2008-10-11 11:02
很有道理,收藏了。。。
1 楼 bohemia 2008-10-10 14:21
没有严格的语法规定,就容易出现兼容问题;

哎.想到JS就想到了恐怖的代码; Flex吧.

发表评论

您还没有登录,请您登录后再发表评论

相关推荐

  • RFC2617-cn 中文版

    RFC2617-cn 中文版 RFC2617-cn 中文版

  • rfc2617 PHP,HTTP认证的底层技术简析与揭秘

    写在前面的话HTTP认证实现的基础是Web服务器与浏览器之间能够安全地交换类似用户名和密码这样的用户凭证,我们也可以把HTTP认证当作是摘要验证(Digest Authentication),这种预定义方法/标准在HTTP协议中使用了编码技术和MD5加密哈希。在这篇文章中,我们将会跟大家详细讨论一下HTTP认证所采用的技术和标准。为了方便大家的理解,本文将使用我们自己编写的一个php脚本,它可以方...

  • RFC2617- HTTP Authentication自译本-(2)

     1 授权鉴别(Access Authentication) 1.1对HTTP/1.1规范的依赖(Reliance on the HTTP/1.1 Specification)        本规范和HTTP/1.1规范[2]一起使用,它使用HTTP/1.1文档2.1节的补充反馈方式(Augmented BNF),并依赖于该文档对非终端(non-terminals)的定义及对其

  • 基于RFC2617的身份认证

    设备及网络用户接入事件及图片存储服务器(公安行业称视频图像信息数据库)时均须经过用户认证。       第一次访问事件及图片存储服务器资源时,需先执行注册操作,事件及图片存储服务器会提示需进行用户名和密码的校验;接口中传递userName、password 、md5,事件及图片存储服务器采用基于口令的数字摘要认证方式(RFC2617)进行设备及用户认证,认证通过后即可访问事件及图片...

  • RFC 2617中文版(HTTP Digest Authentication)

    RFC2617描述了HTTP Digest Authentication,这个是中文版,根据原英文版进行了校正,并重新排版。

  • RFC2617- HTTP Authentication自译本-(2) (转)

    RFC2617- HTTP Authentication自译本-(2) (转)[@more@] 1 授权鉴别(Access Authentication) XML:namespace prefix = o ns = "...

  • Java 基于 IETF RFC 2617 身份认证

    领域:realm 用户名:username 密码:password 请求地址(请求地址中,ip端口之后的部分):digestURI HTTP请求:method 当前计数:nonceCount 客户端计数:clientNonce 保护质量:qop 服务器计数(第一次访问返回401的请求头中携带):nonce a)HA1=MD5(A1)=MD5(username:realm:password) b)HA2=MD5(A2)=MD5(method:digestURI) c)response=MD5(HA1:non

  • GAT1400的注册流程RFC2617认证

    Authorization 请求头字段 response:客户端根据算法算出的摘要值 username:要认证的用户名 realm:认证域,可取任意标识值 uri:请求的资源位置 qop:保护质量 nonce:服务器密码随机数 nc:16进制请求认证计数器,第一次 00000001 algorithm:默认MD5算法 cnonce:客户端密码随机数 Request-Digest 摘要计算过程 import org.apache.commons....

  • HTTP——八、确认访问用户身份的认证

    本篇学习自图解HTTP的第八章、确认访问用户身份认证,详细内容包含了何为认证 BASIC认证 BASIC认证的认证步骤 三、DIGEST认证 DIGEST认证的认证步骤 四、SSL客户端认证 1、SSL 客户端认证的认证步骤 2、SSL 客户端认证采用双因素认证 3、SSL 客户端认证必要的费用五、基于表单认证 1、认证多半为基于表单认证 2、Session 管理及 Cookie 应用等等

  • http协议之digest(摘要)认证

    参考网址: http://www.faqs.org/rfcs/rfc2617.html http://www.faqs.org/rfcs/rfc1321.html http://www.cnblogs.com/my_life/articles/2285649.html http://blog.sina.com.cn/s/blog_53b15ed5010006t9.html http://

  • RFC2617- HTTP Authentication自译本-(1)

       Network Working Group                                          J. FranksRequest for Comments: 2617                       Northwestern UniversityObsoletes: 2069                            

  • RFC2617中文版

    RFC2617中文版 http digest auth

  • 关于http 401的实现和RFC2617协议

    请问tomcat+struts2用http+xml技术怎么支持http401(https?) 还有RFC2617要怎么实现

  • rfc2617 PHP,jsDAV-JavaScript中文网-JavaScript教程资源分享门户

    jsDAV allows you to easily add WebDAV support to a NodeJS application. jsDAV is meant to cover the entire standard, and attempts to allow integration using an easy to understand API.jsDAVjsDAV allows ...

  • 学习RFC2617的收获

    天天很闲,于是组长让我看一下RFC2617,一个同事要用,但不知具体含义。于是看了两个半天,心得如下。对于qop="auth", algorithm="MD5",各值按如下得到: cnonce:MD5(随机数)response:MD5(A1+":"+nonce+":"+nc+":"+cnonce+":"+qop+":"+A2)A1=MD5(username+":"+realm+":"+pw

  • Jakarta Commons HttpClient 学习笔记

    .skiplink{display:none}; .code { background-color: #EFEFEF; padding: 5px; font-family: Courier New, Courier, monospace; font-size: 110%; margin-bottom: 5px; margin-top: 5

Global site tag (gtag.js) - Google Analytics