本文总结了JavaScript开发中七个高效技巧,包括对象与数组的字面量简写、JSON数据格式应用、三元运算符简化逻辑、利用Math.max处理边界值、split/join操作CSS类名、事件委托优化性能以及模块模式隔离全局变量。掌握这些技巧有助于提升代码简洁性与可维护性。
引言
我从事JavaScript编程已有很长时间,具体起始年份已难以考证。对于这门语言近年来的发展与成就,我深感兴奋,并幸运地从中受益。尽管我撰写了大量文章、章节以及一本专著,但仍不断发现新的知识点。以下分享的是一些曾让我惊叹的编程技巧,建议立即尝试,而非等待未来偶然发现。
简洁写法
简洁写法
JavaScript中我最喜欢的特性之一是创建对象和数组的简写语法。过去,若要创建一个对象,代码通常如下所示:
var car = new Object();
car.colour = 'red';
car.wheels = 4;
car.hubcaps = 'spinning';
car.age = 4;
以下写法可实现相同效果:
var car = {
colour:'red',
wheels:4,
hubcaps:'spinning',
age:4
}
这样更加简洁,无需重复使用对象名称。此时car变量已定义。需要注意的是,在IE浏览器中可能会遇到invalidUserInSession的问题,只要确保右大括号前不添加逗号,即可避免此类麻烦。
另一个便捷的简写方式适用于数组。传统定义数组的方法如下:
var moviesThatNeedBetterWriters = new Array(
'Transformers','Transformers2','Avatar','IndianaJones 4'
);
简写版本如下:
var moviesThatNeedBetterWriters = [
'Transformers','Transformers2','Avatar','IndianaJones 4'
];
对于数组,这里存在一个误区:实际上并不存在真正的“图组”功能。但常有人像定义上述car对象那样定义数组,写法如下:
var car = new Array();
car['colour'] = 'red';
car['wheels'] = 4;
car['hubcaps'] = 'spinning';
car['age'] = 4;
数组并非万能工具;这种写法是错误的,容易引起混淆。图组实际上是对象的功能,人们常将两者概念混淆。
另一个非常酷的简写方式是使用三元条件运算符。无需编写如下代码:
var direction;
if(x < 200){
direction = 1;
} else {
direction = -1;
}
…
你可以使用三元条件运算符简化逻辑: var direction = x < 200 ? 1 : -1; 当条件为true时取问号后的值,否则取冒号后的值。
使用JSON格式存储数据
使用JSON格式存储数据
在发现JSON之前,我使用各种复杂方法将数据存储于JavaScript固有数据类型中,例如数组、字符串,并夹杂容易拆分的标志符号及其他繁琐操作。Douglas Crockford发明JSON后,情况彻底改变。使用JSON,你可以利用JavaScript原生功能将数据以复杂格式存储,无需额外转换即可直接访问。JSON是“JavaScript Object Notation”的缩写,它结合了上述两种简写方法。因此,若要描述一个乐队,你可以这样编写:
var band = {
"name":"The Red Hot Chili Peppers",
"members":[
{
"name":"Anthony Kiedis",
"role":"lead vocals"
},
{
"name":"Michael 'Flea' Balzary",
"role":"bass guitar, trumpet, backing vocals"
},
{
"name":"Chad Smith",
"role":"drums,percussion"
},
{
"name":"John Frusciante",
"role":"Lead Guitar"
}
],
"year":"2009"
}
你可以在JavaScript中直接使用JSON,将其封装在函数中,甚至作为API的返回值形式。这被称为JSON-P,许多API都采用这种形式。 你可以调用数据源,在script代码中直接返回JSON-P数据:
<div id="delicious"></div><script>
function delicious(o){
var out = '<ul>';
for(var i=0;i<o.length;i++){
out += '<li><a href="' + o[i].u + '">' +
o[i].d + '</a></li>';
}
out += '</ul>';
document.getElementById('delicious').innerHTML = out;
}
</script>
<script src="http://feeds.delicious.com/v2/json/codepo8/javascript?count=15&callback=delicious"></script>
这是调用Delicious网站提供的Web service功能,获取JSON格式的最近无序书签列表。
基本上,JSON是最轻量级描述复杂数据结构的方法,且能在浏览器中运行。你甚至可以在PHP中使用json_decode()函数来处理它。JavaScript的内置函数(Math、Array和String)中,令我惊讶的是,深入研究这些函数后,我发现它们能极大简化编程工作。使用它们,你可以省去复杂的循环处理和条件判断。例如,过去寻找数字数组中最大数时,我会编写如下循环:
var numbers = [3,342,23,22,124];
var max = 0;
for(var i=0;i<numbers.length;i++){
if(numbers[i] > max){
max = numbers[i];
}
}
alert(max);
现在无需循环即可实现:
var numbers = [3,342,23,22,124];
numbers.sort(function(a,b){return b - a});
alert(numbers[0]);
需要注意的是,你不能对数字字符数组进行sort()操作,因为此时它仅按字母顺序排序。若想了解更多用法,可阅读这篇关于sort()的优秀文章。
另一个有趣的函数是Math.max()。该函数返回参数中最大的数字: Math.max(12,123,3,2,433,4); // returns 433 由于该函数能校验数字并返回最大值,因此可用于测试浏览器对某特性的支持情况:
var scrollTop=Math.max(
doc.documentElement.scrollTop,
doc.body.scrollTop
);
这是解决IE兼容性问题的一种方法。你可以获取当前页面的scrollTop值,但根据页面DOCTYPE的不同,上述两个属性中仅有一个存放该值,另一个为undefined。因此,你可以通过使用Math.max()获取该数值。阅读相关文章可了解更多关于使用数学函数简化JavaScript的知识。
另外,split()和join()是一对非常有用的字符串操作函数。最具代表性的例子是编写一个为页面元素附加CSS样式的功能。
具体而言,当给页面元素附加CSS class时,要么它是该元素的第一个CSS class,要么它已有一些class,需在已有class后加空格再追加新class。当你要去掉这个class时,也需要去掉其前面的空格(这在旧版浏览器中非常重要,因为某些老浏览器无法识别后面跟着空格的class)。
因此,原始写法如下:
function addclass(elm,newclass){
var c = elm.className;
elm.className = (c === '') ? newclass : c+' '+newclass;
}
你可以使用split()和join()函数自动完成此任务:
function addclass(elm,newclass){
var classes = elm.className.split(' ');
classes.push(newclass);
elm.className = classes.join(' ');
}
这会确保所有class都被空格分隔,且你要追加的class正好位于最后。
事件委派
事件委派
Web应用由事件驱动运行。我喜欢事件处理,尤其是自定义事件。它能使产品具备可扩展性,而无需修改核心代码。然而存在一个重大问题(也可视为强大功能的体现),即移除页面上的事件监听器。你可以为某个元素安装事件监听器,使其开始工作。但页面上并无指示表明存在监听器。由于这种不可见性(尤其令新手头疼),以及像IE6这样的“浏览器”在使用过多事件监听器时会出现内存问题,你不得不承认尽量减少事件编程是明智之举。
于是,事件委托应运而生。
当页面上某个元素触发事件时,若该元素的所有子元素在DOM继承关系上也能接收此事件,此时你可以使用父元素上的事件处理器来处理,而非使用一堆子元素上的事件监听器。具体含义是什么?举例说明,页面上有许多超链接,你不想直接操作这些链接,而是希望通过一个函数调用链接,HTML代码如下:
<h2>Great Web resources</h2>
<ul id="resources">
<li><a href="http://opera.com/wsc">Opera Web Standards Curriculum</a></li>
<li><a href="http://sitepoint.com">Sitepoint</a></li>
<li><a href="http://alistapart.com">A List Apart</a></li>
<li><a href="http://yuiblog.com">YUI Blog</a></li>
<li><a href="http://blameitonthevoices.com">Blame it on the voices</a></li>
<li><a href="http://oddlyspecific.com">Oddly specific</a></li>
</ul>
常见做法是通过循环这些链接,将每个链接附加一个事件处理器:
// 典型的事件处理例子
(function(){
var resources = document.getElementById('resources');
var links = resources.getElementsByTagName('a');
var all = links.length;
for(var i=0;i<all;i++){
// Attach a listener to each link
links[i].addEventListener('click',handler,false);
};
function handler(e){
var x = e.target; // Get the link that was clicked
alert(x);
e.preventDefault();
};
})();
我们仅用一个事件处理器也能完成此任务:
(function(){
var resources = document.getElementById('resources');
resources.addEventListener('click',handler,false);
function handler(e){
var x = e.target; // get the link tha
if(x.nodeName.toLowerCase() === 'a'){
alert('Event delegation:' + x);
e.preventDefault();
}
};
})();
因为点击事件发生在这些页面元素内,你只需比较它们的nodeName,找出应响应该事件的元素即可。
免责声明:上述两个事件示例在所有浏览器中均可运行,除IE6外。在IE6上,你需要使用一种特定的事件模型,而非简单的W3C标准实现。这也是推荐使用工具包的原因。
这种方法的好处不仅限于将多个事件处理器缩减为一个。试想,若需动态向链接表追加更多链接,使用事件委托后无需其他修改;否则,你需要重新循环链接表,并为每个链接重新安装事件处理器。
匿名函数和模块化
匿名函数和模块化
JavaScript中最令人懊恼的事情之一是变量缺乏作用域。任何变量、函数、数组、对象,只要不在函数内部,均被视为全局变量。这意味着页面上的其他脚本也可以访问它,甚至覆盖重写它。
解决方法是将变量置于匿名函数内部,定义完成后立即调用。例如,以下写法将产生三个全局变量和两个全局函数:
var name = 'Chris';
var age = '34';
var status = 'single';
function createMember(){
// [...]
}
function getMemberDetails(){
// [...]
}
若页面上的其他脚本中存在名为status的变量,麻烦便会发生。若将它们封装在myApplication中,问题即可解决:
var myApplication = function(){
var name = 'Chris';
var age = '34';
var status = 'single';
function createMember(){
// [...]
}
function getMemberDetails(){
// [...]
}
}();
然而,这样一来函数外部便无功能可用。若此为你所需,则无妨。你还可以省略函数名称:
(function(){
var name = 'Chris';
var age = '34';
var status = 'single';
function createMember(){
// [...]
}
function getMemberDetails(){
// [...]
}
})();
若想在函数外部使用内部内容,则需进行修改。为了能访问createMember()或getMemberDetails(),需将它们变为myApplication的属性,从而暴露于外部世界:
var myApplication = function(){
var name = 'Chris';
var age = '34';
var status = 'single';
return{
createMember:function(){
// [...]
},
getMemberDetails:function(){
// [...]
}
}
}();
//myApplication.createMember() 和
//myApplication.getMemberDetails() 就可以使用了。
这被称为module模式或singleton。Douglas Crockford多次讨论过这些,Yahoo User Interface Library (YUI)中对此有大量应用。但这样一来令我感到不便的是,我需要改变句式以使函数和变量能被外界访问。更甚者,调用时还需加上myApplication前缀。因此,我不喜欢这样做,我更愿意简单地将需被外界访问的元素指针导出。这样做后,反倒简化了外界调用的写法:
var myApplication = function(){
var name = 'Chris';
var age = '34';
var status = 'single';
function createMember(){
// [...]
}
function getMemberDetails(){
// [...]
}
return{
create:createMember,
get:getMemberDetails
}
}();
//现在写成 myApplication.get()和 myApplication.create() 就行了。
我将其称为“revealing module pattern”。
可配置化
可配置化
一旦我将编写的JavaScript代码发布,便有人想修改它,通常是希望它完成原本无法完成的任务——但通常也是因为我编写的程序不够灵活,未提供用户自定义功能。解决方法是为脚本增加配置项对象。我曾撰写一篇深入介绍JavaScript配置项对象的文章,要点如下:
----在脚本中添加名为configuration的对象。 ----该对象内存放人们使用脚本时常需修改的内容: **CSS ID和类名称; **按钮名称、标签文字等; **诸如“每页显示图片数”的值,“图像显示尺寸”的值; **地点、位置及语言设置。 ----将该对象作为公共属性返回给用户,以便用户修改覆盖。 通常情况下,这是编程过程中最后一步要做的事情。我将这些集中体现在一个例子中:“Five things to do to a script before handing it over to the next developer.”
实际上,你也希望代码能方便人们使用,并根据各自需求进行一些调整。若实现此功能,你会减少收到那些抱怨脚本的困惑邮件,这些信件会告知你,有人修改了你的脚本且效果良好。
与后台交互
与后台交互
在多年的编程经历中,我领悟到重要一点:JavaScript是优秀的界面交互开发语言,但若用于处理数字或访问数据源,则略显力不从心。
最初学习JavaScript时,我是为了替代Perl,因为我讨厌必须将代码拷贝到cgi-bin文件夹下才能运行Perl。后来我明白,应使用后台语言处理主要数据,而不能事事由JavaScript承担。更重要的是要考虑安全性和语言特性。
若访问Web service,我可以获取JSON-P格式数据,在客户端浏览器中进行各种数据转换。但当我拥有服务器时,我有更多方法转换数据,可在Server端生成JSON或HTML格式数据返回给客户端,以及执行缓存数据等操作。若事先了解并准备这些,你将长期受益,省去许多烦恼。编写适用于各种浏览器的程序是浪费时间,请使用工具包!
在我最初从事Web开发时,访问页面时究竟使用document.all还是document.layers的问题上痛苦挣扎了很久。我选择了document.layers,因为我喜欢每个层都是独立document的思想(且我编写了大量document.write来生成元素)。层模式最终失败,于是我开始使用document.all。当Netscape 6公布仅支持W3C DOM模型时,我很高兴,但用户并不关心这些。用户只是看到该浏览器无法显示大多数浏览器能正常显示的内容——这是我们的编码问题。我们编写了短视的代码,仅能在当前环境下运行,而不幸的是,运行环境却在不断改变。
我已浪费太多时间处理各种浏览器版本的兼容性问题。善于处理此类问题为我提供了良好的工作机会。但现在我们不必再忍受这种痛苦。
一些工具包,例如YUI、jQuery以及Dojo,都能帮我们处理此类问题。它们通过抽象各种接口实现来处理浏览器问题,如版本不兼容、设计缺陷等,将我们从痛苦中解救出来。除非你要测试某个Beta版浏览器,千万不要在自己的程序中添加修正浏览器缺陷的代码,因为你很可能在浏览器已修复该问题时却忘了删除你的代码。
另一方面,完全依赖工具包也是短视行为。工具包可帮你快速开发,但若不深入理解JavaScript,你也会犯错。
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持本站。