本文整理并详解了“不唐突的JavaScript”的七条核心准则。内容涵盖停止盲目假设、利用HTML钩子与节点关系、通过CSS优化DOM遍历、理解浏览器行为、掌握事件处理机制、以及通过命名空间避免全局污染。旨在帮助开发者编写更健壮、可维护且对用户友好的JavaScript代码。
停止假设:JavaScript的不可靠性
(JavaScript是一个不可靠的助手)
实现“不唐突的JavaScript”最关键的一点在于:彻底摒弃任何先入为主的假设。
* 不要默认JavaScript环境可用,应预设其可能不可用,避免直接依赖JS功能。
* 在未通过测试确认浏览器支持特定方法和属性前,不要假设它们可用。
* 不要假设HTML结构符合预期,每次使用时都必须进行检查,若不可用则不执行任何操作。
* 确保JavaScript功能与输入设备无关。
* 需意识到其他脚本可能干扰当前JS功能,因此应尽可能确保脚本作用域的安全与隔离。
在编写脚本前,首要任务是检查目标HTML结构,寻找有助于实现功能的元素。
2.找出钩子和节点关系
利用HTML钩子与节点关系
(HTML是脚本的基石)
编写JS前,先审视目标HTML。若HTML结构混乱或未知,很难设计出优秀的脚本方案——结果往往是创建过多标记,或使应用过度依赖JavaScript。
在HTML中,有两个关键要素需要考虑:钩子(Hooks)和节点关系。
<1>.HTML 钩子
HTML最基础且最重要的钩子是ID,它可通过最快的DOM方法getElementById访问。若文档中所有ID唯一(尽管IE中name与ID存在bug,但优秀类库已解决此问题),使用ID是安全、可靠且易于测试的。
其他钩子包括HTML元素标签和CSS类。HTML元素可通过getElementsByTagName访问,而多数浏览器原生DOM方法尚不支持直接访问CSS类。不过,许多外部类库提供了类似getElementsByClassName的方法。
<2>.HTML 节点关系
HTML另一个有趣之处在于标记间的关系,需思考以下问题:
* 如何以最少DOM遍历最便捷地到达目标节点?
* 修改哪些标记能最大程度访问需修改的子节点?
* 给定元素具备哪些属性或信息可用于定位其他元素?
DOM遍历资源消耗大且速度慢,因此应尽量利用浏览器已内置的技术来实现。
3.把遍历交给专家来做
通过CSS优化DOM遍历
(CSS,更快地遍历DOM)
令人意外的是,许多开发者对使用DOM方法(如getElementsByTagName、nextSibling、previousSibling、parentNode等)遍历DOM感到困惑。有趣的是,我们早已通过另一种技术——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来实现样式控制。若通过JS为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,也能创建完全不同的界面。拖拽、折叠区域、滚动条和滑块均可用JS实现,但这不仅是技术问题,还需思考:
* 新界面能否独立于输入设备?若不能,依赖哪些机制?
* 新界面是否遵循浏览器或其他富界面准则(例如,能否直接用鼠标切换多级菜单,还是必须使用Tab键?)
* 哪些功能是依赖JavaScript的,而你又必须提供?
最后一个问题并非难题,因为如需,可使用DOM凭空创建HTML。例如“打印”链接,因浏览器未提供非JS的打印文档功能,需用DOM创建此类链接。同理,可点击的标题栏实现内容展开/收缩也属此类。标题栏无法被键盘激活,但链接可以。因此,需用JS添加链接,使键盘用户也能展开/收缩内容模块。
解决此类问题的极佳资源是设计模式库。至于识别哪些浏览器功能独立于输入设备,需依靠经验积累。首要理解的是事件处理机制。
5.理解事件
掌握事件处理与委托
(事件处理会引起改变)
事件处理是迈向“不唐突的JavaScript”的第二步。重点不在于让所有元素可拖拽、可点击或添加内联处理,而在于理解事件处理是完全可分离的模块。我们已将HTML、CSS和JS分离,但在事件处理的分离上尚未深入。
事件处理器监听文档元素上的变化。若发生事件,处理器会找到一个奇妙的对象(通常名为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
}
} 2下一页阅读全文