当与其它程序员共同开发代码时(这里对大多数合作或团队项目来说是很常见的),为了保持你们的清醒而维护良好的编程惯例将会变得极其重要。随着近年来JavaScript已经开始得到认可,专业程序员所编写的JavaScript代码量急剧增加。这种观念上的转变和JavaScript的使用导致围绕它的开发惯例得到了长足的发展。
在这一章里,我们将学到一些清理、更好地组织代码,并改进代码质量使其能够为他人所用的一些方法。
标准化面象对象代码
编写可重用代码的第一个也是最重要的步骤就是以一种贯穿整个应用程序的标准方式编写你的代码,尤其是面向对象的代码。通过上一章的面向对象的JavaScript的运作方式,你可以看到JavaScript语言相当灵活,允许你模拟许多种不同的编程风格。
作为开端,设计出一种最符合你需要的编写面向对象代码并实现对象继承(把对象的属性克隆到新的对象里)的体制是很重要的。然而表面看来,每一个写过一些面向对象的JavaScript代码的人都已经建立起了各自的实现方案,这可能相当令人困惑的。在这一节中,我们将弄清JavaScript的中继承是怎样工作的,随后了解几种不同的供选择的辅助方法的原理以及怎样将它们应用于你的程序当中。
原型继承
JavaScript使用了一种独特的对象创建和继承的方式,称为原型继承(prototypal inheritance)。这一方法背后的前提(相对大多数程序员所熟悉的传统的类/对象方案而言)是,一个对象的构造器能够从另一个对象中继承方法,建立起一个原型对象,所有的新的对象都将从这个原型创建。
这整个过程由prototype属性(存在于每一个函数中,因为任何函数都可以是一个构造器)促成。原型继承是为单继承设计的;尽管如此,仍然存在可以实现多继承的手段,我将在下一节中讨论。
使得这种形式的继承特别难以掌握的是,原型并不从其它的原型或者其它的构造器继承属性,而是从实际的对象中继承。程序3-1展示了prototype属性怎样被用于简单继承的几个例子。
程序3-1. 原型继承的例子
代码:
//创建Person对象的构造器
function Person( name ) {
this.name = name;
}
//为Person对象加入一个新方法
Person.prototype.getName = function() {
return this.name;
};
//创建一个新的User对象构造器
function User( name, password ) {
//注意这并不支持优雅的重载/继承,
//如能够调用超类的构造器
this.name = name;
this.password = password;
};
//User对象继承Person对象的全部方法
User.prototype = new Person();
//我们添加一个自己的方法给User对象
User.prototype.getPassword = function() {
return this.password;
};
上例中最重要的一行是User.prototype = new Person();。我们来深入地看看这到底意味着什么。User是对User对象的函数构造器的引用。new Person()建创一个新的Person对象,使用Person构造器。将这一结果设为User构造器的prototype的值,这意味着不论任何时候你使用new User()的时候,新建的User类对象也将拥有你使用new Person()时创建的Person类对象的所有方法。
带着这一特殊的技巧,我们来看一些不同的开发者所编写的使得JavaScript中继承的过程简单化的封装。
类继承
类继承(classical inheritance)是多数开发者所熟悉的一种形式,拥有带方法的可被实例化为对象的类。对初学面向对象JavaScript的程序员来说这种情况是非常典型的:试图模拟这种程序构思,却很少真正悟出怎样正确地实现。
值得感激的是,JavaScript大师之一,Douglas Crockford,把开发一套能用于JavaScript模拟类式继承的简单方法做为了他的目标,如他在网站上所解释的那样(http://javascript.crockford.com/inheritance.html)。
程序3-2展示了他所编写的三个函数,用来建立起一种类风格的JavaScript继承的综合形式。每个函数实现了继承的一个方面:继承单个函数,继承单个父类的全部,和从多个父类中继承独立的方法。
程序3-2. Douglas Crockford的使用JavaScript模拟类形式继承的三个函数
代码:
//一个简单的辅助函数,允许你为对象的原型绑定新的函数
Function.prototype.method = function(name, func) {
this.prototype[name] = func;
return this;
};
//一个(相当复杂的)函数,允许你优雅地从其它对象中继承函数,
//同时仍能调用"父"对象的函数
Function.method('inherits', function(parent) {
//追踪所处的父级深度
//Keep track of how many parent-levels deep we are
var depth = 0;
//继承parent的方法
//Inhert the parent's methods
var proto = this.prototype = new parent();
//创建一个名为uber的新的特权方法,
//调用它可以执行在继承中被覆盖的任何函数
//Create a new 'priveledged' function called 'uber', that when called
//executes any function that has been written over in the inheritance
this.method('uber', function uber(name) {
var func; //将被执行的函数(The function to be execute)
var ret; // 该函数的返回值(the return value of then function)
var v = parent.prototype; //父类的prototype(The parent's prototype)
//如果已经位于另一"uber"函数内
//If we're already within another 'uber' function
if (depth) {
//越过必要的深度以找到最初的prototype
//Go the necessary depth to function the orignal prototype
for ( var i = d; i > 0; i += 1 ) {
v = v.constructor.prototype;
}
//并从该prototype取得函数
//and get the functin from that prototype
func = v[name];
//否则,这是第一级的uber调用
//Otherwise, this is the first 'uber' call
} else {
//从prototype中取得函数
//Get the function to execute from the prototype
func = proto[name];
//如果该函数属于当前的prototype
//If the function was a part of this prototype
if ( func == this[name] ) {
//则转入parent的prototype替代之
//Go to the parent's prototype instead
func = v[name];
}
}
//记录我们位于继承栈中的'深度'
//Keep track of how 'deep' we are in the inheritance stack
depth += 1;
//使用用第一个参数后面的所有参数调用该函数
//(第一个参数保有我们正在执行的函数的名称)
//Call the function to execute with all the arguments but the first
//(whick holds the name of the function that we're executing)
ret = func.apply(this, Array.prototype.slice.apply(arguments, [1]));
//重置栈深度
//Reset the stack depth
depth -= 1;
//返回执行函数的返回值
//Return the return value of the execute function
return ret;
});
return this;
});
//一个用来仅继承父对象中的几个函数的函数,
//而不是使用new parent()继承每一个函数
Function.method('swiss', function(parent) {
//遍历所有要继承的方法
for (var i = 1; i < arguments.length; i += 1) {
//要导入的方法名
var name = arguments[i];
//将方法导入这个对象的prototype
this.prototype[name] = parent.prototype[name];
}
return this;
});
我们来看看这三个函数到底提供给我们些什么,以及为什么我们应该使用它们而不去试图写出我们自己的原型继承模型。这三个函数的前提是简单的:
Function.prototype.method:此函数是为构造器的prototype附加函数的简单方式。这一特殊的子句能够工作是因为所有的构造器都是函数,故能获得新的方法"method"。
Function.prototype.inherits:这一函数能用来提供简单的单父继承。函数代码的主体围绕着在你的对象的任何方法中调用this.uber("方法名")使之执行它所重写了的父对象的方法的能力。这是JavaScript继承模型本身不具备的一个方面。
Function.prototype.swiss:这是.method()函数的一个高级版本,能用来从一个父对象中抓取多个方法。当将它分别用于多个父对象时,你将得到一种实用的多父继承的形式。
对上面三个函数提供给我们什么有了一个大致的了解之后,程序3-3重拾你在3-1中所见的Person/User的例子,不过这次使用了新的类风格的继承。另外,你可以看看在改善程序清晰性方面,这个库能够提供怎样的额外功能。
程序3-3. Douglas Crockford的类继承式JavaScript函数的例子。
代码:
//创建一个新的Person对象构造器
function Person( name ) {
this.name = name;
}
//给Person对象添加方法
Person.method( 'getName', function(){
return name;
});
//创建新一个新的User对象构造器
function User( name, password ) {
this.name = name;
this.password = password;
},
//从Person对象继承所有方法
User.inherits( Person );
//给User对象添加一个新方法
User.method( 'getPassword', function(){
return this.password;
});
//重写新Person对象创建的方法,
//但又使用uber函数再次调用它
User.method( 'getName', function(){
return "My name is: " + this.uber('getName');
});
尝试过使用一个可靠的继承加强的JavaScript库所带来的可能性之后,我们再来关注其它的一些广通用的流行的方法。
Base库
JavaScript对象创建和继承领域近期的成果是Dean Edwards所开发的Base库。这一特别的库提供了一些不同的方式来扩展对象的功能。除此之外,它甚至提供了一种直觉式的对象继承方式。Dean最初开发这个库是为了用于他的其它的项目,包括IE7项目(作为对IE一整套的升级)。Dean的网站上列出的例子相当易于理解并确实很好的展示了这个库的能力:http://dean.edwards.name/weblog/2006/03/base 。除此而外,你可以在Base源代码目录里找到更多的例子:http://dean.edwards.name/base/。
Base库是相当冗长而复杂的,它值得用额外的注释来说明(包含于http://www.apress.com/的Source Code/Download所提供的代码中)。除了通读注释过的代码以外,强烈建议你去看Dean在他的网站上提供的例子,因为它们非常有助于澄清常见的疑惑。
但作为起点,我将带你一览Base库的几个可能对你的开发很有帮助的重要的方面。具体地,在程序3-4展示了类创建、单父继承和重写父类函数的例子。
程序3-4. 利用Dean Edwards的Base库进行简单的类创建和继承的例子
代码:
//创建一个新的Person类
var Person = Base.extend({
//Person类的构造函数
constructor: function( name ) {
this.name = name;
},
//Person类的简单方法
getName: function() {
return this.name;
}
});
//创建一个新的继承了Person类的User类
var User = Person.extend({
//创建User类的构造器,
constructor: function( name, password ) {
//该构造器顺次调用了父类的构造器方法
this.base( name );
this.password = password;
},
//为User类创建另一个简单的方法
getPassword: function() {
return this.password;
}
});
我们来看看在程序3-4中Base库是如何达到先前所归纳的三个目标从而创造出一种对象创建和继承的简单形式的。
Base.extend(...);:这一表达式用来创建一个新的基本的构造器对象。此函数授受一个参数,即一个简单的包含属性和值的对象,其中的属性都会作为原型方法被被增添到(所创建的构造器)对象中。
Person.extend(...);:这是Base.extend()语法的一个可替换版本。所有的创建的构造器都使用.extend()方法获取它们自己的.extend()方法,这意味着直接从它们继承是可能的。程序3-4中,正是通过直接从最初的Person构造器中直接继承的方式创建了User构造器。
this.base();:最后,this.base()方法用来调用父对象的被重写了的对象。你会发现这与Corockford's的类继承所使用的this.uber()函数截然是截然不同的,你无需提供父类的方法名(这一点有助于真正地清理并明晰化你的代码)。在所有的面向对象的JavaScript库中,Base库的重写父方法的功能是最好的。
个人而言,我觉得Dean的Base库能够出产最可读的、实用的和可理解的面向对象的JavaScript代码。当然,最终选择什么库要看开发者自己觉得什么最适合他。接下来你将看到面对对象的JavaScript代码如何在流行的Prototype库中实现。
Prototype库
Prototype是一个为了与流行的"Ruby on Rails"web框架协同工作而发的JavaScript库。不要把库的名字与构造器的prototype属性混淆——那是只一种令人遗憾的命名情况。
撇开命名不谈,Prototype库使得JavaScript外观和行为上者更接近于Ruby。为达到这一点,Prototype的开发者们利用了JavaScript的面向对象本质,并且附加了一些函数和属性给核心的JavaScript对象。不幸的是,该库根本不是由它的创造者们给出文档的;而幸运的是它写得非常清晰,而且它的一些用户介入编写了他们自己版本的文档。你们可以在Prototype的网站(http://prototype.conio.net/)上随意地浏览完整的代码,从文章"Painless JavaScript Using Prototype"里得到Prototype的文档。
在这一节里,我们将仅着眼于Prototype用于创建其面象对象结构并提供基本继承的特定的函数和对象。程序3-5展示了Prototype使用的达到此目标的全部代码。
程序3-5. Prototype所使用的模拟面向对象JavaScript代码的两个函数
代码:
//创建一个名为"Class"的全局对象
var Class = {
//它拥有一个用来创建新的对象构造器的函数
create: function() {
//创建一个匿名的对象构造器
return function() {
//调用它本身的初始化方法
this.initialize.apply(this, arguments);
}
}
}
//为对象"Object"添加静态方法,用以从一个对象向另一个对象复制属性
Object.extend = function(destination, source) {
//遍历欲扩展的所有属性
for (property in source) {
//并将它添加到目标对象
destination[property] = source[property];
}
//返回修改过的对象
return destination;
}
Prototype确实只用了两个明显的函数来创建和维护其整个面向对象体系。你们可能已发现,仅通过看观察代码,也能断定它不如Base或者Crockford的类式方法那样强大。两个函数的前提很简单:
Class.create():这个函数简单地返回一个可用做构造器的匿名函数包装。这个简单的构造器做了一件事:调用和执行对象的initialze属性。这意味着,你的对象里至少有一个包含函数的initialize属性;否则,代码将会出错。
Object.extend():这个函数简单地从一个对象往另一个对象复制属性。当你使用构造器的prototype属性时你能设计出一种更简单的继承的形式(比JavaScript中可用的缺省的原型继承更简单)。
既然你已经了解了Prototype的底层代码是如何工作的,程序3-6展示了一些例子,说明它在Prototype库自身中是怎样用来通过添加功能层来扩展天然的JavaScript对象的。
程序3-6. Prototype怎样使用面对对象函数扩展JavaScript中字符串的缺省操作的例子。
代码:
//为String对象的原型添加额外的方法
Object.extend(String.prototype, {
//一个新的stripTags函数,删除字符串中的所有HTML标签
stripTags: function() {
return this.replace(/<\/?[^>]+>/gi, '');
},
//将一个字符串转换成一个字符的数组
toArray: function() {
return this.split('');
},
//将文本"foo-bar"转换成'骆驼'文本"fooBar"(译注:fooBar中间的大写字符像是驼峰吧)
//Converts "foo-bar" text to "fooBar" 'camel' text
camelize: function() {
//以'-'拆分字符串
var oStringList = this.split('-');
//若字符串中没有'-'则提前返回
if (oStringList.length == 1)
return oStringList[0];
//随意地"骆驼化"字符串的开头
//Optionally camelize the start of the string
var camelizedString = this.indexOf('-') == 0
? oStringList[0].charAt(0).toUpperCase() + oStringList[0].substring(1)
/*
译注:this.indexOf('-')==0,那oStringList[0]显然就是空字符串了,
有必要toUpperCase加substring吗?
*/
: oStringList[0];
//将后继部分的首字母大写
for (var i = 1, len = oStringList.length; i < len; i++) {
var s = oStringList[i];
camelizedString += s.charAt(0).toUpperCase() + s.substring(1);
}
//返回修改的字符串
return camelizedString;
}
});
//stripTags()方法的一个例子
//可以看到它删除了字符串中的所有HTML
//只保留纯文本
"<b><i>Hello</i>, world!".stripTags() == "Hello, world!"
//toArray()方法的一个例子
//我们将得到字符串中的第四个字符
"abcdefg".toArray()[3] == "d"
//camelize()方法的例子
//它将原字符串转换成新的格式
"background-color".camelize() == "backgroundColor"
接下来,让我们再一次回到这章我所用到的那个有着User和Person对象且User对象从Person对象继承属性的例子。使用Prototype的面向对象风格的代码,见程序3-7。
程序3-7. Prototype的用于创建类和实现简单继承的辅助函数
代码:
//用名义上的构造器创建一个Person对象
var Person = Class.create();
//将下列的函数复制给Person的prototype
Object.extend( Person.prototype, {
//此函数立即被Person的构造器调用
initialize: function( name ) {
this.name = name;
},
//Person对象的简单函数
getName: function() {
return this.name;
}
});
//用名义上的构造器创建一个User对象
var User = Class.create();
//User对象从其父类继承所有属性
User.prototype = Object.extend( new Person(), {
//用新的初始化函数重写原来的
initialize: function( name, password ) {
this.name = name;
this.password = password;
},
//为对象添加一个新的函数
getPassword: function() {
return this.password;
}
});
尽管Prototype库所提出的面向对象技术不是革命性的,它们也强大到足以帮助开发者创建更简单、更易编写的代码了。然而,如果你正将编写数量巨大的面向对象代码,最终你可能更趋向于选择Base这样的库来辅助你的工作。
接下来我们将探讨怎样处理你的面向对象的代码,并使之准备好被其它的开发者或库所使用并与之相合。
封装
完成你的漂亮的面向对象JavaScript代码编写以后(或者之前,如果你够聪明的话),是时候改善它使之能够与其它JavaScript库和谐共处了。意识到你的代码将被其他的可能需求跟你完全不同的开发者或者用户使用,这一点非常重要。尽可能编写最整洁的代码对此将有所帮助,但是从他人已经做到的去学习,也会达到同样的效果。
在这一节里,你将会看到成千上万的开发者日常使用的几个大型的库。每种库都提供了独特的方式来管理其结构,使之容易学习和使用。另外,你将会看到用来清理你的代码的几种方式,以便于尽可能提供最好的体验给其他人。
命名空间
一个重要和简单的可用来清理和简化你的代码的技术是命名空间(namespacing)的概念。JavaScript目前并不缺省地支持命名空间(不像Java或Python,比如说),因此我们不得不设法以一种相似且能够满足需要的技术来实现。
实际上,JavaScript里不存在固有的命名空间的这种东西。但是,利用JavaScript的前提,即所有的对象都能拥有属性,属性又能依次包含其它对象,你可以创造出起一种看起来和工作起来都跟你在其它语言中使用的命名空间极其相似的东西。使用这种技术,你可以建立起如程序3-6所示的独特的结构。
程序3-8. JavaScript中的命名空间及其实现
代码:
//创建一个缺省的,全局的命名空间
var YAHOO = {};
//设置一些子命名空间,使用对象
YAHOO.util = {};
//创建最终的命名空间,包含作为属性的函数
YAHOO.util.Event = {
addEventListener: function(){ … }
};
//在特定的命名空间里调用函数
YAHOO.util.Event.addEventListener( … )
我们来考察几种不同的浏行的库中使用命名空间的例子,以及它们对于一致的、可扩展的、插入式的体系结构有着怎样的益处。
Dojo
Dojo是一个极其流行的框架,提供给开发者建造完整的web应用程序所需的一切。这意味着有大量的需被包含和独立评估的子库,否则整个库简直会庞大到不能处理。关于Dojo的更多信息可以在其项目站点上找到:http://dojotoolkit.org/。
Dojo有一整套的围绕JavaScript命名空间的封装系统。基于这一系统你可以动态地导入新的程序包,它们会自动地执行以备使用。程序3-9展示了Dojo里使用的命名空间的一个例子。
程序3-9. Dojo中的封装和命名空间
代码:
<html>
<head>
<title>Accordion Widget Demo</title>
<!-- 包含Dojo框架 -->
<script type="text/javascript" src="dojo.js"></script>
<!-- 包含不同的Dojo包 -->
<script type="text/javascript">
//导入两个不同的包,
//用来创建Accordian Container widget("可折叠容器"界面工具)
dojo.require("dojo.widget.AccordionContainer");
dojo.require("dojo.widget.ContentPane");
</script>
</head>
<body>
<div dojoType="AccordionContainer" labelNodeClass="label">
<div dojoType="ContentPane" open="true" label="Pane 1">
<h2>Pane 1</h2>
<p>Nunc consequat nisi vitae quam. Suspendisse sed nunc. Proin…</p>
</div>
<div dojoType="ContentPane" label="Pane 2">
<h2>Pane 2</h2>
<p>Nunc consequat nisi vitae quam. Suspendisse sed nunc. Proin…</p>
</div>
<div dojoType="ContentPane" label="Pane 3">
<h2>Pane 3</h2>
<p>Nunc consequat nisi vitae quam. Suspendisse sed nunc. Proin…</p>
</div>
</div>
</body>
</html>
Dojo的封装体系结构非常强大且值得一看,如果你有志于使用JavaScript维护大型的代码库。除此之外,就其库的庞大而论,你也一定能从中发现一些对你有用的功能。
YUI
Yahoo UI(http://developer.yahoo.com/yui/)是另一个维护了巨大的命名空间化封装体系结构的JavaScript库。这个库被设计用来为许多常见的web应用(如拖放)提供实现或解决方案。所有这些UI元素分布于层次结构中。Yahoo UI的文档相当的出色,其完整和细致值得注意。
与Dojo相似,Yahoo UI也使用了深度的命名空间层次来组织其函数和功能。但是,与Dojo不同的是,任何外部代码的"导入"由你来明确地完成,而不是通过导入语句。程序3-10是一个展示Yahoo UI库中命名空间是什么子和怎样工作的例子。
程序3-10. Yahoo UI中的封装和命名空间
代码:
<html>
<head>
<title>Yahoo! UI Demo</title>
<!-- 导入Yahoo UI的主体库 -->
<script type="text/javascript" src="YAHOO.js"></script>
<!-- 导入事件包 -->
<script type="text/javascript" src="event.js"></script>
<!-- 使用导入的Yahoo UI库 -->
<script type="text/javascript">
//Yahoo UI的所有事件和实用功能都包含命名空间YAHOO里,
//并被细分为小一些的命名空间,如'util'
YAHOO.util.Event.addListener( 'button', 'click', function() {
alert( "Thanks for clicking the button!" );
});
</script>
</head>
<body>
<input type="button" id="button" value="Click Me!"/>
</body>
</html>
无论Dojo还是Yahoo UI都在于单个大型的封装里组织和维护大量的代码方面有非常杰出的表现。当需要实现你自己的封装架构时,理解它们是怎样使用JavaScript命名空间做到这一点的将带来极大的帮助。
清理你的代码
在我开始调试和编写测试案例的话题(这正是下一章要做的)之前,首先检查你是怎么编写代码、把它准备好为其他人所用,这是必不可少的。如果你想要你的代码在历经其他开发者的使用和修改仍能生存,你将需要保证你的代码中没有任何语句能被误会或错误地使用。尽管你也可以手工进行整理代码的工作,使用工具来帮助标记出难以处理的以后可能会出麻烦的代码片段通常更有效率。这正是JSLint的用武之地。JSLint有一系列的内建的规则用来标记出以后可能会带来麻烦的代码片段。在JSLint的网站上有一个完整的分析器http://www.jslint.com/。另外,JSLint的所有规则和设置可以在这里找到:http://www.jslint.com/lint.html。
JSLint是Douglas Crockford开发的另一个工具,体现了他自己的编码风格,因此如果你不喜欢或不太信奉他要求的一些更改,不依它们就是。然而,规则中的一部分的确非常有意义,我将在下面对它们进行特别的说明。
变量声明
JSLint提出的一个明智的要求是,程序中出现的所有变量都必须在被使用之间声明。尽管JavaScript不明确要求你进行变量声明,不这么做可能会导致其实际作用域的混乱。比如说,如果你要为一个没有在一个函数之内声明的变量赋值,那个变量的作用域将是函数内还是全局的?这不是仅凭观察代码就能立即弄清的,因而需要明朗化。JSLint的变量声明惯例的例子见程序3-11。
程序3-11. JSLint要求的变量量声明
!=与== vs. !==与===
开发者易犯的一个常见的错误是对JavaScript中false值的缺乏理解。在JavaScript里,null,0,"",false,和undefined全部彼此相等(==),因为它们的计算值都为false。这意味着如果你使用代码test==false,则它在test为undefined或null时,也会得到结果true,这可能并非你所期望的。
这正是!==和===有用的地方。这两个操作符都将检查变量的精确值(比如null),而不是单看其计算值(如false)。JSLint要求你任何时候你使用==和!=进行真假判断时,都必须用!==或===替代。程序3-12展示了这些操作符有怎样的不同。
程序3-12. !=与!==区别于!==和===的示例
块与花括号
这是一条我比较难以接受的规则,但是在一个共享的代码环境里,遵照它仍然是有意义的。这条规则背后的前提是,不能使用单行的块。但你有一个子句(如if(dog==cat))且只有一个语句在其中(dog=false;),你可以省略通常需要的花括号;这对于while()和for()语句的块同样成立。尽管这是JavaScript提供的一个很好的便捷,省略代码中的括号对那些没有意识到哪些代码是属于块哪些代码不属于的人来说却可能会导致奇怪的结果。程序3-13很好地解释了这一状况。
程序3-13. 缩进不当的单行代码块
代码:
//这是合法的、正常的JavaScript代码
if ( dog == cat )
if ( cat == mouse )
mouse = "cheese";
//JSLint要求它像这样书写
if ( dog == cat ) {
if ( cat == mouse ) {
mouse = "cheese";
}
}
分号
最后这一点被证明在下一节中所谈到的代码压缩中是最重要的。在JavaScript里,如果你每行书写一条语句的话,语句末的分号是可选的。省略了分号的未压缩代码可能是好的,但是一旦你删除换行符以削减文件尺寸,问题就产生了。为了避免这一点,你应该总是记得在语句的结尾处包含分号,如程序3-14所示。
程序3-14. 需要分号的语句
代码:
//如果你打算压缩你的JavaScript代码,务必在所有语句的结尾加上分号
var foo = 'bar';
var bar = function(){
alert('hello');
};
bar();
在这分号这一话题里我们最终引接触了JavaScript代码压缩的概念。使用JSLint编写整洁的代码对其它开发者和你自己有益处,代码压缩却最终是对你的用户最有用的,因为那样他们就能更快地开始使用你的站点。
压缩
分发JavaScript库的一个不可缺少的方面是使用代码压缩来节省带宽。压缩应该作为把你的代码投入产品之前的最后一步来使用,因为它会使你代码混乱难以辩认。有三种类型的JavaScript压缩器:
简单地删除无关的空白字符和注释,仅保留必须的代码的压缩器;
删除空白和注释,也同将所有的变量名变得改短的压缩器;
删除空白和注释,同时还最小化代码中所有单词(而不仅仅是变量名)的压缩器。
我将要介绍两不同的库:JSMin和Paker。JSMin属第一类压缩器(删除无关的非代码)而Paker属第三类(彻底压缩所有单词)。
JSMin
JSMin的原理很简单。它检查一块JavaScript代码并删除所有非必须字符,仅保留纯粹的起作用的代码。JSMin通过简地删除所有无关的空白字类字符(包括tab的换行符)和所有的注释。压缩器的在线版本可以在这里找到:http://www.crockford.com/
javascript/jsmin.html。
为了对代码传给JSMin以后到底发生了什么有一个直观的感觉,我们举一个简单的代码块的例子(如程序3-15所示),传给压缩器,再看看得到的输出结果(程序3-16所示)。
程序3-15. 用来判断用户浏览器的代码
代码:
// (c) 2001 Douglas Crockford
// 2001 June 3
// The -is- object is used to identify the browser. Every browser edition
// identifies itself, but there is no standard way of doing it, and some of
// the identification is deceptive. This is because the authors of web
// browsers are liars. For example, Microsoft's IE browsers claim to be
// Mozilla 4. Netscape 6 claims to be version 5.
var is = {
ie: navigator.appName == 'Microsoft Internet Explorer',
java: navigator.javaEnabled(),
ns: navigator.appName == 'Netscape',
ua: navigator.userAgent.toLowerCase(),
version: parseFloat(navigator.appVersion.substr(21)) ||
parseFloat(navigator.appVersion),
win: navigator.platform == 'Win32'
}
is.mac = is.ua.indexOf('mac') >= 0;
if (is.ua.indexOf('opera') >= 0) {
is.ie = is.ns = false;
is.opera = true;
}
if (is.ua.indexOf('gecko') >= 0) {
is.ie = is.ns = false;
is.gecko = true;
}
程序3-16. 3-15所示程序的压缩版本
代码:
var is={ie:navigator.appName=='Microsoft Internet Explorer',java:navigator.javaEnabled(),ns:navigator.appName=='Netscape',ua:navigator.userAgent.toLowerCase(),version:parseFloat(navigator.appVersion.substr(21))||parseFloat(navigator.appVersion),win:navigator.platform=='Win32'} is.mac=is.ua.indexOf('mac')>=0;if(is.ua.indexOf('opera')>=0){is.ie=is.ns=false;is.opera=true;}if(is.ua.indexOf('gecko')>=0){is.ie=is.ns=false;is.gecko=true;}
注意到所有的空白和注释都被删掉了,大大减小了代码的总尺寸。
JSMin可能是最简单的JavaScript代码压缩工具。这是一种很好的在你的产品代码中开始使用压缩的起步方式。然而,当你准备好节约更多的带宽时,你将会想要升级到使用Paker这一令人敬畏的、极其强大的JavaScript代码库。
Paker
Paker是目前为止可得到的最强大的JavaScript压缩器。它由Dean Edwards开发,以一种彻底地削减代码尺寸并在运行时重新扩展和执行的方式工作。通过使用这种技术,Paker创造出可能的理想地最小化的代码。你可以把它当成是为JavaScript代码设计的自解压的ZIP文件。该压缩器可用的在线版本见http://dean.edwards.name/paker/。
Paker的脚本十分庞大且非常复杂,建议你不要妄图自己去实现它。另外,它生成的代码有几百字节的代码头(使之能够释放其自己身),因而对于极小的代码它并不理想(JSMin对此最好一些)。然而,对于大型的文件,它绝对是完美的。程序3-17摘自Paker所生成的自解压代码。
程序3-17. Packer压缩生成代码的一部分
代码:
eval(function(p,a,c,k,e,d){e=function(c){return c.toString(36)};if(!''.replace(/^/,String)){while(c--){d[c.toString(a)]=k[c]||c.toString(a)}k=[(function(e){returnd[e]})];e=(function(){return'\\w+'});c=1};while(c--){if(k[c]){p=p.replace(newRegExp('\\b'+e(c)+'\\b','g'),k[c])}}return p}('u 1={5:2.f==\'t sr\',h:2.j(),4:2.f==\'k\',3:2.l.m(),n:7(2.d.o(p))||7(2.d),q:2.g==\'i\'}1.b=1.3.6(\'b\')>=0;a(1.3.6(\'c\')>=0){1.5=1.4=9;1.c=e}a(1.3.6(\'8\')>=0){1.5=1.4=9;1.8=e}',31,31,'|is|navigator|ua|ns|ie….
压缩(尤其是使用Paker)压缩你的代码的作用,是不可小视的。具体情况依赖于你的代码是怎么写的,通常你可以将其尺寸减少超过50%,为你的用户改善页面载入时间,这应该是任何JavaScript应用程序的重要目标。
分发
JavaScript编写过程的最后一步是可选的,这最依赖于你的个别的情况。如果你只是为你自己或者一个公司编写代码,最通常的情况是你简单地把你的代码分发给其它开发者或者上传到你的站点上供使用。
然而,如果你开发了一段有趣的代码并且希望全世界能够任意地使用它,这就是诸如JavaScript Archive Network(JASN)之类的服务发挥作用的时候了。JSAN是由几个喜爱CPAN(Comprehensive Perl Archive Network)的功能的用途的Perl开发者发起的。更多关于JSAN的消息可以在其站点http://openjsan.org/找到。
JSAN要求提交的所有模块都以良好格式化的面向对象风格写成,遵从它特定的体系结构。除了中心代码仓库之外,JSAN有一套方法,通过它可以导入你的代码所请求的外部JSAN模块依赖。这使得编写相互依赖的应用程序变得极其简单,无须担心用户已经安装了哪些模块。为了理解典型的JSAN模块是怎样工作的,我们来看一个简单的,DOM.Insert(可从些网址得到http://openjsan.org/doc/r/rk/rki ... b/
DOM/Insert.html)。
这一特定的模块授受一个HTML字符串并将它插入到网页中的指定位置。除了出色地面向对象以外,这个模块还引用并加载了两个其它的JSAN模块。如程序3-18所示。
程序3-18. 一个简单的JSAN模块(DOM.Insert)
代码:
//我们将试图使用JSAN包含一些其它的模块
try {
//载入两所需的JSAN库
JSAN.use( 'Class' )
JSAN.use( 'DOM.Utils' )
//如果JSAN没有被加载,将抛出异常
} catch (e) {
throw "DOM.Insert requires JSAN to be loaded";
}
//确保DOM命名空间存在
if ( typeof DOM == 'undefined' )
DOM = {};
//创建一个新的DOM.Insert构造器,继承自"Object"
DOM.Insert = Class.create( 'DOM.Insert', Object, {
//接受两个参数的构造器
initialize: function(element, content) {
//向其中插入HTML的元素
this.element = $(element);
//欲插入的HTML字符串
this.content = content;
//尝试使用IE的方式插入HTML
if (this.adjacency && this.element.insertAdjacentHTML) {
this.element.insertAdjacentHTML(this.adjacency, this.content);
//否则,使用W3C方式
} else {
this.range = this.element.ownerDocument.createRange();
if (this.initializeRange) this.initializeRange();
this.fragment = this.range.createContextualFragment(this.content);
this.insertContent();
}
}
});
编写清晰的面向对象的、易交互的JavaScript代码的能力应该是你的开发活动的品质证明。正是基于这一方式,我们将建造和探究JavaScript语言的其它部分。随着JavaScript继续得到更多人的认同,这种编程风格的重要性将会只增不减,变得更加的有用和盛行。
本章摘要
在本章中你看到了建造可重用代码结构的几种不向方法。运用你在前面章节所学的面向对象技术,你有能够利用它们创建最适合多开发者环境的干净的数据结构。另外,你看到了创建可维护的代码、削减JavaScript文件大小和为分发而封装代码的最好的几种方法。了解了怎样编写漂亮地格式的可维护的代码将会为你省去无数次的挫败。
相关推荐
### 精通javascript+jquery电子书第一章pdf #### JavaScript的起源与发展 JavaScript是一种重要的编程语言,对于网页开发者而言,掌握它意味着能够创造出更加互动、动态且用户友好的网页体验。本书第一章首先从...
2. 第二章:JavaScript基础 - 掌握JavaScript的基本语法,如变量、数据类型、运算符等。 - 学习JavaScript内置对象,如Date、Array、Math等,并解决实际问题。 3. 第三章:JavaScript面向对象编程 - 理解...
#### 第二章:浏览器中的JavaScript 本章节重点介绍了JavaScript在浏览器环境下的工作原理。包括如何与HTML文档交互、DOM操作的基本方式等。此外,还会介绍事件处理机制以及如何利用JavaScript提升用户体验。 #### ...
第14章 面向对象 第15章 PHP加密技术 第16章 MySQL数据库基础 第17章 phpMyAdmin图形化管理工具 第18章 PHP操作MySQL数据库 第19章 ADODB类库 第20章 Zend Framework框架 第3篇 高级应用 第21章 Smarty模板技术 第22...
#### 第二部分:JavaScript核心 **第2章:浏览器中的JavaScript** - **2.1 嵌入网页的可执行内容** - **2.1.1 在什么地方装载JavaScript代码** - 解释了如何将JavaScript代码嵌入到HTML文档中。 - **2.1.2 关于...
由于文件很大,下载地址在压缩包里面 本书是目前最深入的Javas cript图书,讲述了现代Javas ...第二部分 专业Javas cript开发 第2章 面向对象的Javas cript 14 2.1 语言特性 14 2.1.1 引用 14 ..............
全书共分24章,包括初识PHP、PHP环境搭建和开发工具、PHP语言基础、流程控制语句、字符串操作、正则表达式、PHP数组、PHP与Web页面交互、PHP与JavaScript交互、日期和时间、Cookie与Session、图形图像处理技术、文件...
本书从初学者角度出发,通过通俗易懂的语言,丰富多彩的实例,详细介绍了使 ...全书共分24章,包括初识PHP、PHP ...本站提供的PHP从入门到精通第二版 pdf扫描版资源来源互联网,版权归该下载 资源的合法拥有者所有
\ 第4章 程序主窗口—— QMainWindow 卢传富 Qt应用程序的主窗口是由多个部件/组件构成的框架,本章通过一个简单文本编辑器的例子,介绍了主窗口的菜单、工具条、中心部件、锚接部件和状态条,并通过Qt设计器绘制和...
\ 第8章 文件处理 蔡志明介绍了Qt的文件处理,包括基于流的文本文件和二进制文件处理,文件信息和目录操作,目录以及文件的变化监控,文件引擎的编写。 219 \ 第9章 网络 李立夏介绍了Qt的网络处理,包括编写常见的...
第二部分 基础知识篇 第2章 Canvas基本功能 第3章 Canvas高级功能 第4章 lufylegend开源库件 第三部分 开发实战篇 第5章 从简单做起—“石头剪子布”游戏 第6章 开发“俄罗斯方块”游戏 第7章 开发“是男人就下一百...
05 面向对象概念总结 06 反射 07 反射及动态导入模块 08 类的内置attr属性 09 类内置attr属性补充 10 继承的方式完成包装 11 组合的方式完成授权 第27章 01 os模块复习 02 上节课复习 03 内置函数补充及...
"文档树"在前面第二章我们已经提到过,它是文档元素分级结构的形象表示。一个文档结构树包含根元素,根元素是最顶级的元素,(就是紧接着XML声明语句后的第一个元素)。看例子: <filelist> <title>... ...
Python是一种高级程序设计语言,以其解释型、面向对象和动态数据类型的特点而闻名。它也是一种脚本语言,如同JavaScript,其最大优势在于不需要预先编译,可以直接解释执行,这使得Python编写代码更加灵活和便捷。与...
书名:jQuery权威指南(系统介绍jQuery方方面面,囊括118个实例和2个综合案例,实战性强) 作者:陶国荣 著 书号:978-7-111-32543-7 定价:59.00元 出版社:机械工业出版社华章公司 ...第11章 综合案例开发...