JavaScript中new String和”"的区别

2010年4月2日 11:32 by webphoenix

[转]评论:程序员,不止干到35岁

2010年3月11日 20:13 by webphoenix

  在中国,程序员不能超过35岁,似乎已经是不争的事实,软件开发工作就是青春饭,顶多靠毕业这十年的时间,超过这个年龄,要不成功跃身成为管理者,要不转行进入其他领域,好像再没有更好的选择,即使偶有继续坚持作开发的,也被看成另类一族。老外却不是这样,40,50岁仍然作开发工作的大有人在,甚至很多牛人,包括获得康柏最佳软件奖的Robber,仍在埋头作着他所喜欢的编码工作,为何如此差异。我仔细想了一下,特别是这一年参加了太多的技术培训,基本有了一些结论。

  首先,国内软件开发工作的性质决定这一切,我们所作的产品开发,真正技术含量并不高,高等院校毕业的学生,经过几年的培训和实践,基本都能掌握这些技能,而我们现有产品开发设计水平有限,对于软件开发的理解还仅限于编码,完成功能这些基本的需求,产品进度是第一驱动力,特别是一些小公司,把快速开发出产品作为最高目标,软件设计质量差,很少考虑可维护性,可扩展性这些重要的软件质量属性,仅仅追求功能和进度,产品以最快的速度完成设计,编码,少有测试甚至没有测试,就直接交付给客户,在客户那里暴露出各种各样的问题出来,开发人员就开始不断得为客户解决这样那样的问题,软件做到最后,很多人都会陷入自己不断挖掘出来的陷阱里走不出来,步入在复杂的系统中查找BUG,解决BUG,增加功能,引入新的BUG,再解决BUG,这样永无休止的恶性循环中,从事的都是一些低级开发工作,对于软件开发的理解无法上升到更高的层次,每天都疲于救火和拼命打补丁,软件维护成本居高不小,即使最终摆脱了这样的厄运项目结束,下一次软件开发又是同样过程的重复,这些无休止的低级开发和维护工作,会耗光一个人所有的激情和精力,随着年龄的增大,热情和体力都在衰减,而这些工作,只要毕业生经过培训,同样可以胜任,有着廉价的,并且充满热情的毕业生作竞争,身心疲惫的老程序员必然面对更大的竞争压力,做到最后,连他们自己都会失去对软件开发的兴趣,甚至会产生厌倦的情绪,对于这样的快速开发模式,十年开发经验和五年开发经验有很大区别吗?干吗不使用更经济便宜的开发人员。

  另一个原因,是我们中国人一向的官本位思想,开发人员始终处于一种很尴尬的境界,听起来从事的是高级技术工作,说起来也是以人为本,但是,有多少企业管理者真正重视和认可开发人员的,哪怕是硕士,博士,这些开发人员在大官,小官面前,都只是普通劳动者而已。程序员是一个没有未来的行业,哪怕资历再深,能力在强,也还是一个劳动者而已,只有当上领导,走上管理才是最终的出路,否则,薪水上不去,地位上不去,只能面对被淘汰的命运。

  实际不然,之所以出现这种问题,是国内对软件开发的一种错误理解。如果软件开发仅仅是代码编写,查找BUG,可以说,那实在是一些低级的软件行为,做了十年的开发人员,确实不见得比作了3年的人更强多少,反正是简单重复的工作,工作3年的一样可以作,而且有更大的热情和精力。但是,应该跳出这个怪圈,走向更高的领域,对软件开发重新进行认识。要明白,软件开发要融入工程化的思想,要有高度的设计能力,关注更多的软件运行属性和设计质量属性,同样的系统,不同的软件人员会做出完全不同的软件,正规,高效的开发团队要比游击队开发团队不只高出几十倍。软件开发,不仅仅关注功能,性能,更应该去研究可维护性,可扩展性等质量属性,不仅仅只关注代码,而更要上升为设计,复用等。软件开发不仅仅是写出C语言的代码,单元测试,同行评审,培训与学习,这同样是开发人员基本的素质,也是提高软件质量的最基本手段。举例来说,单元测试做好了,就可以让开发效率大大提高,也能让软件的维护成本降下来。把开发人员解放出来。同行评审可以让开发人员之间更多的交流和沟通,在设计,编码阶段严格控制软件质量,培训则是提高自身技术水平,从理论上提升自己的最好方法。先前这些主要的质量行为都被忽略了,而这恰恰是走出低级开发的最好途径。同时,还有更高层次的设计开发,软件重构,模块化,系统构架,搭建统一软件平台,度量软件质量等等,有了重构,就会写出可读性好,可维护性好的软件,甚至因此提高自己的软件设计能力,减少出错的几率;模块化,让软件最大程度的实现复用,提高软件开发效率和开发质量。软件构架设计,从技术层次上最早决定了软件的命运,而这里又有太多的技术,设计模式,软件规划等,让软件开发从编码上升到设计的层次,搭建统一软件平台,是大产品,系列化开发的必然之路,对于软件质量的度量,会让我们的软件开发变得更加透明,清晰,让我们更清楚的把握到软件开发过程,从各个环节去控制软件质量,而不是仅仅凭着感性的认识到了最后才去补救。

  以上这些知识,一个人,哪怕勤奋学习一辈子,也不可能完全具备所有的技能。35岁算什么,做到70岁都不晚。当然,你需要真正的热爱它,并且不断得学习和进修。所以说,软件开发人员,要作的事情太多了,软件实在是一个浩大的脑力工程,甚至会让人觉得这是一个投入/产出比最低的行业,因为要学习的知识实在太多了,每时每刻都要处于不断的学习和更新当中,呵呵,既然选择了,既然喜欢了,就投入进去,享乐于此了。最重要的是,我们自己能并且愿意去认识到这一点,跳出低级开发的怪圈,走向更高的层次

  同时,要意识到,一个资深开发人员的作用绝不亚于一个管理人员,高明的企业应该给予相同的待遇,要把培养和储备高级技术人才作为高科技企业的重点发展方向,这样才能让企业在人才领域得到不断积累,资深开发人员才会带出高效的开发团队,从而开发出高质量,高效率的软件,让企业在不断激烈的竞争中取胜。要明白,不是每个人都适合或者乐于转向管理的,很多人就是适合做开发工作,他们可以走得更深入,成为专家,应该充分发挥出他们的能力,让整个团队高效运作起来。

  所以说,开发人员,决不只是35岁,只要你愿意,可以作一生的开发,从中获得更多的快乐。
  本文转自:CSDN

技术文章 , ,

VC8.0中使用JavaScript引擎V8

2010年2月1日 18:52 by webphoenix

1.获取V8的最新源代码,Google上有详细的获取方法
2.编译V8,这个Google上也有详细的教程,在这里提醒一下,你可以使用VC8编译V8,也可以使用scons命令编译,最好是编译两个版本,一个Debug版,一个Release版,这样可以方便调试时使用
3.新建VC8工程,在工程的目录下新建一个include目录,然后把 v8/include下的v8.h文件复制到自己建立的include目录下
4.工程目录下新建lib目录,将编译出来的v8的debug和Release版本的lib文件复制到这里
5.资源视图下右键单击项目,选择属性,弹出工程的属性对话框,选择“配置属性” -> “链接器” -> “输入” 在“附加依赖项”处输入v8的lib文件,如 lib/v8_release.lib,注意这里你需要根据你的程序模式输入不同的lib文件,如果你是Debug模式,你必须输入debug版的v8.lib,否则可能会引起异常。
6.在程序的.h或.cpp文件的输入下面这段:

#include "include/v8.h"
using namespace v8;

7.在你需要的地方添加上相关的v8调用代码
8.编译运行

注意如果编译的时候出现:
v8_debug.lib(platform-win32.obj) : error LNK2019: 无法解析的外部符号 __imp__timeGetTime@0,该符号在函数 “public: void __thiscall v8::internal::Time::SetToCurrentTime(void)” (?SetToCurrentTime@Time@internal@v8@@QAEXXZ) 中被引用
请在using namespace v8前加上对 winmm.lib 的调用,代码如下:

#include "include/v8.h"
#pragma comment(lib,"winmm.lib")
using namespace v8;

基本步骤就是这么多,暂时还没有出现什么异常

技术文章 , , , , ,

[转]JavaScript的陷阱

2010年1月17日 14:24 by webphoenix

  这本来是翻译Estelle Weyl的《15 JavaScript Gotchas》,里面介绍的都是在JavaScript编程实践中平时容易出错或需要注意的地方,并提供避开这些陷阱的方法,总体上讲,就是在认清事物本质的基础样要坚持好的编程习惯,其实这就是Douglas Crockford很久以前提出的JavaScript风格要素问题了,有些内容直接是相同的,具体请看《Javascript风格要素(1)》《Javascript风格要素(2)》。在翻译的过程中,我又看到了贤安去年翻译的《JavaScript的9个陷阱及评点》,其内容又有些交叉在一起,所以我就在现有翻译的基础上做了一个简单的拼合,并依据自己的理解增加了一些注释和解释。

  1. 区分大小写:变量名、属性和方法全部都区分大小写
  2. 不匹配的引号、圆括号或花括号将抛出错误
  3. 条件语句:3个常见陷阱
  4. 换行:一直用分号结束语句来避免常见的换行问题
  5. 标点法:在对象声明的尾部逗号将导致出错
  6. HTML id 冲突
  7. 变量作用域:全局变量对局部变量
  8. 函数重载:当重载不存在时,覆盖函数
  9. 区分string.replace()函数不是全局的
  10. parseInt应该包含两个参数
  11. “this”和绑定问题
  12. 为参数设置默认值,以免你遗漏它们
  13. for each循环是用于对象而不是数组
  14. switch语句需要点技巧
  15. 如果你要检查null,应该先检查undefined
  16. 时间处理陷阱

区分大小写

  变量名和函数名都是区分大小写的。就像配错的引号一样,这些大家都知道。但是,由于错误是不作声的,所以这是一个提醒。为自己选择一个命名规则,并坚持它。而且,记住JavaScript中的原生函数和CSS属性都是骆驼拼写法(camelCase)

getElementById('myId') != getElementByID('myId'); //它应该是“Id”而不是“ID”
getElementById('myId') != getElementById('myID'); // “Id”也不等于“ID” 

document.getElementById('myId').style.Color; //返回 "undefined"

不匹配的引号、圆括号或花括号

  避免陷入不匹配的引号、圆括号或花括号陷阱的最好方式是编码时一直同时写出打开和关闭这两个元素符号,然后在其中间加入代码。开始:

var myString = ""; //在输入字符串值之前写入这对引号
function myFunction(){
 if(){//关闭每个打开的括弧
 }
}
//统计所有的左括号和右括号数量,并且确保它们相等
alert(parseInt(var1)*(parseInt(var2)+parseInt(var3)));
//关闭每个打开的圆括号

  每当你打开一个元素,请关闭它。当你添加了关闭圆括号后,你再把函数的参数放进圆括号中。如果有一串圆括号,统计所有打开的圆括号和所有关闭的圆括号,并且确保这两个数字相等。

条件语句(3个陷阱)

  1. 所有的条件语句都必须位于圆括号中。执行语句主体不管是一句还是多句都强烈建议用花括号包围起来,这样能避免很多因修改或嵌套而产生的潜在错误。
    if(var1 == var2){
    //statement
    }
  2. 不要犯无意地使用赋值运算符的错误:把第二个参数的值赋给第一个参数。因为它是一个逻辑问题,它将一直返回true且不会报错。
    if(var1 = var2){} // 返回true。把var2赋值给var1
  3. JavaScript是弱类型,除了在switch语句中。当JavaScript在case比较时,它是非弱类型。
    var myVar = 5;
    if(myVar == '5'){ //返回true,因为JavaScript是弱类型
     alert("hi");  //这个alert将执行,因为JavaScript通常不在意数据类型
    }
    switch(myVar){
     case '5':
     alert("hi"); //这个alert将不会执行,因为数据类型不匹配
    }
    

换行

  当心JavaScript中的硬换行。换行被解释为表示行结束的分号。即使在字符串中,如果在引号中包括了一个硬换行,那么你会得到一个解析错误(未结束的字符串)。

var bad  = '<ul id="myId">
  <li>some text</li>
  <li>more text</li>
  </ul>'; // 未结束的字符串错误 

var good = '<ul id="myId">' +
 '<li>some text</li>' +
 '<li>more text</li>' +
 '</ul>'; // 正确

  前面讨论过的换行被解释为分号的规则并不适用于控制结构这种情况:条件语句关闭圆括号后的换行并不是给其一个分号。

  一直使用分号和圆括号,那么你不会因换行而出错,你的代码易于阅读,且除了那些不使用分号的怪异源码外你会少一些顾虑:所以当移动代码且最终导致两个语句在一行时,你无需担心第一个语句是否正确结束。

多余的逗号

  在任何JavaScript对象定义中,最后一个属性决不能以一个逗号结尾。Firefox不会出错,而IE会报语法错误。

var theObj = {
        city : "Boston",
        state : "MA",//IE6和IE7中有“缺少标识符、字符串或数字”的错误,IE8 beta2修正了它
  }

HTML id 冲突

  JavaScript DOM绑定(JavaScript DOM bindings)允许通过HTML id索引。在JavaScript中函数和属性共享同一个名字空间。所以,当在HTML中的一个id和函数或属性有相同的名字时,你会得到难以跟踪的逻辑错误。然而这更多是一个CSS最佳实践的问题,当你不能解决你的JavaScript问题时,想起它是很重要的。

<ul>
<li id="length">1</li>
<li id="thisLength">2</li>
<li id="thatLength">3</li>
</ul>
<script>
var listitems = document.getElementsByTagName('li');
var liCount = listitems.length; //IE下返回的是<li id="length">1</li>这个节点而不是所有<li的数量> 

var thisLength = document.getElementById('thisLength');
    thatLength = document.getElementById('thatLength');
    //IE下会出现“对象不支持此属性和方法”的错误,IE8 beta2下首次加载页面会出错,刷新页面则不会
    //在IE中thisLength和thatLength直接表示以其为id值的DOM节点,
    //所以赋值时会出错,当有var声明时,IE会把其当着变量,这个时候就正常了。
</script>

  如果你要标记(X)HTML,绝不要使用JavaScript方法或属性名作为id的值。并且,当你写JavaScript时,避免使用 (X)HTML中的id值作为变量名。

变量作用域

  JavaScript中的许多问题都来自于变量作用域:要么认为局部变量是全局的,要么用函数中的局部变量覆盖了全局变量。为了避免这些问题,最佳方案是根本没有任何全局变量。但是,如果你有一堆,那么你应该知道这些陷阱。

  不用var关键字声明的变量是全局的。记住使用var关键字声明变量,防止变量具有全局作用域。在下面例子中,在函数中声明的变量具有全局变量,因为没有使用var关键字声明:

anonymousFuntion1 = function(){
 globalvar = 'global scope'; //全局声明,因为“var”遗漏了
 return localvar;
}();
alert(globalvar); //弹出“global scope”,因为函数中的变量是全局声明 

anonymousFuntion2 = function(){
 var localvar = 'local scope'; //使用“var”局部声明
 return localvar;
}();
alert(localvar); //错误 “localvar未定义”。没有全局定义localvar

  作为参数引进到函数的变量名是局部的。如果参数名也是一个全局变量的名字,像参数变量一样有局部作用域,这没有冲突。如果你想在函数中改变一个全局变量,这个函数有一个参数复制于这个全局变量名,记住所有全局变脸都是window对象的属性。

var myscope = "global";
function showScope(myscope){
 return myscope; //局部作用域,即使有一个相同名字的全局变量
}
alert(showScope('local'));

function globalScope(myscope){
 myscope = window.myscope; //全局作用域
 return myscope;
}
alert(globalScope('local'));

  你甚至可以在循环中声明变量:

for(var i = 0; i < myarray.length; i++){}

覆盖函数/重载函数

  当你不止一次的声明一个函数时,这个函数的最后一次声明将覆盖掉该函数的所有前面版本且不会抛出任何错误或警告。这不同于其他的编程语言,像Java,你能用相同的名字有多重函数,只要它们有不同的参数:调用函数重载。在JavaScript中没有重载。这使得不能在代码中使用JavaScript核心部分的名字极其重要。也要当心包含的多个JavaScript文件,像一个包含的脚本文件可能覆盖另一个脚本文件中的函数。请使用匿名函数和名字空间。

(function(){
 // creation of my namespace 创建我的名字空间
 if(!window.MYNAMESPACE) {
  window['MYNAMESPACE'] = {};
 }
 //如果名字空间不存在,就创建它
 //这个函数仅能在匿名函数中访问
 function myFunction(var1, var2){
  //内部的函数代码在这儿
 }
 // 把内部函数连接到名字空间上,使它通过使用名字空间能访问匿名函数的外面
 window['MYNAMESPACE']['myFunction'] = myFunction;
})(); // 圆括号 = 立即执行
// 包含所有代码的圆括号使函数匿名

  这个例子正式为了实现解决上一个陷阱“变量作用域”的最佳方案。匿名函数详细内容请看《Javascript的匿名函数》YUI整个库只有YAHOO和YAHOO_config两个全局变量,它正是大量应用匿名函数和命名空间的方法来实现,具体请看《Javascript的一种模块模式》

字符串替换

  一个常见错误是假设字符串替换方法的行为会对所有可能匹配都产生影响。实际上,JavaScript字符串替换只改变了第一次发生的地方。为了替换所有发生的地方,你需要设置全局标识。同时需要记住String.replace()的第一个参数是一个正则表达式。

var myString = "this is my string";
myString = myString.replace("","%20"); // "this%20is my string"
myString = myString.replace(/ /,"%20"); // "this%20is my string"
myString = myString.replace(/ /g,"%20"); // "this%20is%20my%20string"

parseInt

  在JavaScript得到整数的最常见错误是假设parseInt返回的整数是基于10进制的。别忘记第二个参数基数,它能是从2到36之间的任何值。为了确保你不会弄错,请一直包含第二个参数。

parseInt('09/10/08'); //0
parseInt('09/10/08',10); //9, 它最可能是你想从一个日期中得到的值

  如果parseInt没有提供第二个参数,则前缀为 ‘0x’ 的字符串被当作十六进制,前缀为 ‘0′ 的字符串被当作八进制。所有其它字符串都被当作是十进制的。如果 numString 的前缀不能解释为整数,则返回 NaN(而不是数字)。

‘this’

  另一个常见的错误是忘记使用“this”。在JavaScript对象中定义的函数访问这个对象的属性,但没有使用引用标识符“this”。例如,下面是错误的:

function myFunction() {
 var myObject = {
  objProperty: "some text",
  objMethod: function() {
   alert(objProperty);
  }
 };
 myObject.objMethod();
} 

function myFunction() {
 var myObject = {
  objProperty: "some text",
  objMethod: function() {
   alert(this.objProperty);
  }
 };
 myObject.objMethod();
}

  有一篇A List Apart文章用通俗易懂的英文表达了this绑定的问题。

  对this使用最大的陷阱是this在使用过程中其引用会发生改变:

<input type="button" value="Gotcha!" id="MyButton">
<script>
var MyObject = function () {
    this.alertMessage = "Javascript rules";
    this.ClickHandler = function() {
        alert(this.alertMessage );
        //返回结果不是”JavaScript rules”,执行MyObject.ClickHandler时,
        //this的引用实际上指向的是document.getElementById("theText")的引用
  }
}();
document.getElementById(”theText”).onclick =  MyObject.ClickHandler
</script>

  其解决方案是:

var MyObject = function () {
    var self = this;
    this.alertMessage = “Javascript rules”;
    this.OnClick = function() {
        alert(self.value);
    }
}();

  类似问题的更多细节和解决方案请看《JavaScript作用域的问题》

遗漏的参数

  当给函数增加一个参数时,一个常见的错误是忘记更新这个函数的所有调用。如果你需要在已经被调用的函数中增加一个参数来处理一个特殊情况下的调用,请给这个函数中的这个参数设置默认值,以防万一在众多脚本中的众多调用中的一个忘记更新。

function addressFunction(address, city, state, country){
 country = country || “US”; //如果没有传入country,假设 “US”
 span>//剩下代码
}

  你也能通过获取arguments来解决。但是在这篇文章我们的注意力在陷阱上。同时在《Javascript风格要素(2)》也介绍了||巧妙应用。

for关键字

  在JavaScript中关键字for有两种使用方式,一个是for语句,一个是for/in语句。for/in语句将遍历所有的对象属性(attribute),包括方法和属性(property)。决不能使用for/in来遍历数组:仅在当需要遍历对象属性和方法时才使用for/in。

  1. for(var myVar in myObject)语句用一个指定变量无任何规律地遍历对象的所有属性。如果for/in循环的主体删除了一个还没有枚举出的属性,那么该属性就不在枚举。如果循环主体定义了新属性,那么循环是否枚举该属性则是由JavaScript的实现决定。
  2. for(var 1=0; i < myArray.length; i++)语句会遍历完一个数组的所有元素。

  为了解决这个问题,大体上你可以对对象使用 for … in,对数组使用for循环:

listItems = document.getElementsByTagName('li');
for (var listitem in listItems){
 //这里将遍历这个对象的所有属性和方法,包括原生的方法和属性,但不遍历这个数组:出错了!
}
//因为你要循环的是数组对象,所用for循环
for ( var i = 0; i < listItems.length; i++) {
 //这是真正你想要的
}

  对象的有些属性以相同的方式标记成只读的、永久的或不可列举的,这些属性for/in无法枚举。实际上,for/in循环

会遍历所有对象的所有可能属性,包括函数和原型中的属性。所有修改原型属性可能对for/in循环带来致命的危害,所以需要采用hasOwnProperty和typeof做一些必要的过滤,最好是用for来代替for/in。

switch语句

Estelle Weyl写了一篇switch statement quirks,其要点是:

  1. 没有数据类型转换
  2. 一个匹配,所有的表达式都将执行直到后面的break或return语句执行
  3. 你可以对一个单独语句块使用多个case从句

undefined ≠ null

  null是一个对象,undefined是一个属性、方法或变量。存在null是因为对象被定义。如果对象没有被定义,而测试它是否是null,但因为没有被定义,它无法测试到,而且会抛出错误。

if(myObject !== null  && typeof(myObject) !== 'undefined') {
 //如果myObject是undefined,它不能测试是否为null,而且还会抛出错误
}
if(typeof(myObject) !== 'undefined' && myObject !== null) {
  //处理myObject的代码
}

Harish Mallipeddi对undefined和null有一个说明。

事件处理陷阱

  刚接触事件处理时最常见的写法就是类似:

window.onclick = MyOnClickMethod

  这种做法不仅非常容易出现后面的window.onclick事件覆盖掉前面的事件,还可能导致大名顶顶的IE内存泄露问题。为了解决类似问题,4年前Simon Willison就写出了很流行的addLoadEvent():

function addLoadEvent(func) {
 var oldonload = window.onload;
 if (typeof window.onload != 'function') {
  window.onload = func;
 }else {
  window.onload = function() {
   oldonload();
   unc();
  }
 }
}
addEvent(window,'load',func1,false);
addEvent(window,'load',func2,false);
addEvent(window,'load',func3,false);

  当然在JavaScript库盛行的现在,使用封装好的事件处理机制是一个很好的选择,比如在YUI中就可以这样写:

YAHOO.util.Event.addListener(window, "click", MyOnClickMethod);

  本文转自 http://dancewithnet.com/ 作者:Kaven

技术文章 , ,

[转]Javascript风格要素

2010年1月17日 13:59 by webphoenix

  程序设计是困难的,其核心是管理的复杂性。计算机程序是人类做出的最复杂的东西。质量是不可靠的且隐蔽的。

  好的体系架构是必需给程序足够的结构使其健壮而不会陷入混乱的泥淖,但我们表达一个程序细节的方式是同等重要的。一个程序的本质会被不良的编码所隐藏。只有当一个程序的表达清晰时,我们才能有希望正确的推理出它的效率、安全和正确性。

  William Strunk的《风格要素》(The Elements of Style)是关于文学风格的经典著作,它是一本关于英文写作的薄手册,在用法、组织和形式上提出忠告。风格的理念应用于编程,在1972年Kreitzberg和Shneiderman的《FORTRAN风格要素》(The Elements of FORTRAN Style)中是不成功的,但在1978年的Kernighan和Plauger的《编程风格要素》(The Elements of Programming Style)中是非常成功的:

好的编程不能通过笼统的说教。学习编程的好方法是一次又一次的思考:真正的编程是如何通过一些良好实践的原则和一点常识来进行改进的。

  他们从他们批评和改进的其他的编程教科书中筛选程序。

  当我们在这里谈论风格时,我们谈论的不是潮流或者时尚,也不是CSS、布局惯例或排版。我们正在谈论的是能真正提高代码价值的表达式的永恒品质。对于公司来说,他们的评估和他们的代码是息息相关的,风格应该是一个至关重要的受关注内容。

  我们使用许多编程语言,但在某一方面,Javascript是最重要的,它是浏览器的语言。当人们访问我们的站点时,他们将邀请我们的Javascript程序在他们的机器中执行。我们有义务使那些程序执行好。

  没有好的关于Javascript编程的课本。在网页使用Javascript的大多数人学习它是通过从糟糕的书、糟糕的站点和糟糕的工具中复制相当糟糕的例子。我们这里有极好的Javascript程序员社区,但我们依旧能从较好的风格实践中获益。

  为了证明这个问题,我将从公共网站中抽取一些程序,展示它们如何能被改进。这并不是我有意为难任何人。我的意图仅是通过例子展示风格的价值。我不会保留任何秘密:我展示给你的是我们已经传送给世界上的每一个人。

淘汰过时结构

  下面的例子是2005-09-19摘自www.yahoo.com

<script language=javascript><!--
     lck='',
     sss=1127143538,
     ylp='p.gif?t=1127143538&_ylp=A0Je5ipy2C5D54AAwVX1cSkA',
     _lcs='';
--></script> 

  这个脚本块用了language属性。这个特性是微软为了支持VBScript引入的。然而Netscape采用它是为了支持非标准偏差。W3C不采取这个language属性,倾向使用MIME类型的type属性取代。不幸的是,MIME类型未得到标准化,所以它有时是”text/javascript”、”application/ecmascript”或其他。幸运的是所有的浏览器都选择Javascript作为默认的编程语言,所以简单的写<script>是最好的。它最小,且工作在最多的浏览器。在脚本中使用HTML的注释的时间要回溯到Netscape Navigator和Netscape Navigator 2的兼容问题上来。后者引入了<script>标签。然而,前者的用户能像文本一样看到脚本,因为在HTML惯例中不能识别的标签被忽略。<!–注释hack在Netscape Navigator 3出现的时候是需要的,现在它不被需要了。它是丑陋的且浪费空间的。

  逗号运算符像Javascript语法的大多数一样从C语言中借用。逗号运算符获得两个值,且返回第二个。在语言的定义中它的存在易于掩盖一定的编码错误,编译器也易于对一些错误视而不见。最好避免逗号运算符,并以分号运算符代替。

  在这个案例里,我们定义了一些全局变量。当指定一个未知(匿名)的变量时,Javascript会创建一个新的全局变量来替代产生的错误。事后看来,这是一个错误。即使当他们是一个标准错误,这是避免错误的最好办法。我们应该明确的声明变量。它花费我们四个字符,但是它正是要做的正确的事。

<script>
var lck = '3ek6b0i2he2a5eh3/o',
    sss = 1126894256,
    ylp = 'p.gif?t=1126894256&_ylp=A0Je5iOwCitDw2YBX331cSkA',
    _lcs = '94040';
</script>

  从上面我们能得出这样的原则:淘汰过时结构

结构化语句要始终使用区块

  下面这个例子是一个cookie类构造器。它创建了一个有get和set方法的对象。

function yg_cookie() {
    this.get = function (n) {
        var s,
            e,
            v = '',
            c = ' ' + document.cookie + ';';
        if ((s = c.indexOf((' ' + n + '='))) >= 0) {
            if ((e = c.indexOf(';',s)) == -1)
                e = c.length;
            s += n.length + 2;
            v = unescape(c.substring(s, e));
        }
        return (v);
    }
    this.set = function (n,v,e) {
        document.cookie = n + "=" + escape(v) +
            ";expires=" + (new Date(e * 1000)).toGMTString() +
            ";path=/" + ";domain=www.yahoo.com";
    }
}
var _yc = new yg_cookie();

  Javascript的if语句和C语言的相似:它能执行一个语句或一个区块。关于用语句的问题是一个普通的错误非常难以探测。最好把

if ((e = c.indexOf(';', s)) == -1)
    e = c.length;

写成

if ((e = c.indexOf(';', s)) == -1) {
    e = c.length;
}

  区块的用处是避免了下面的情况:

if ((e = c.indexOf(';', s)) == -1)
    e = c.length;
    s += n.length + 2;

  它将出现当indexOf返回-1是,s是只被增加,但这不是实际情形。像这样的bug被发现代价是非常高的,但是可以通过一直使用大括号声明结构来廉价的避免。

避免赋值表达式

  Javascript从C继承的另一个坏习惯是赋值表达式。它出现在流线型代码中,但它能使控制流更难以理解。如果我们从他们的使用中分离了s和e的计算,get方法会变得更清晰。

this.get = function (n) {
    var v = '',
        c = ' ' + document.cookie + ';',
        s = c.indexOf((' ' + n + '=')),
        e = c.indexOf(';', s);
    if (s >= 0) {
        if (e == -1) {
            e = c.length;
        }
        s += n.length + 2;
        v = unescape(c.substring(s, e));
    }
    return (v);
}

  我们现在能看到当s被计算时,在indexOf参数两边有多余的括号。(在return语句中也有非必须的括号。)但是更重要的是,能容易的看出if (e == -1)的目的是什么:如果cookie中末尾的分号不存在,假定cookie结束在字符串的末端。然而,当我们计算c时,我们在cookie中加入了一个分号,它保证了预料的if条件将绝不会发生。所以我们能移除if。

使用对象参数

  当一个函数被指定一个值,像在this.get = function (n) { … }中,它应该以一个分号来结束所有的赋值语句。

function yg_cookie() {
    this.get = function (n) {
        var v = '',
            c = ' ' + document.cookie + ';',
            s = c.indexOf((' ' + n + '='));
        if (s >= 0) {
            s += n.length + 2;
            v = unescape(c.substring(s, c.indexOf(';', s)));
        }
        return v;
    };
    this.set = function (n,v,e) {
        document.cookie = n + "=" + escape(v) +
            ";expires=" + (new Date(e * 1000)).toGMTString() +
            ";path=/" + ";domain=www.yahoo.com";
    };
}
var _yc = new yg_cookie();

  最后,我们看到yg_cookie是一个能产生一个无状态对象的构造器。我们一点也不需要构造器函数。我们能简单创建一个空对象,通过指派方法的方式来增加它。

var _yc = new Object();
_yc.get = function (n) {
    var v = '',
        c = ' ' + document.cookie + ';',
        s = c.indexOf((' ' + n + '='));
    if (s >= 0) {
        s += n.length + 2;
        v = unescape(c.substring(s, c.indexOf(';', s)));
    }
    return v;
};
_yc.set = function (n,v,e) {
    document.cookie = n + "=" + escape(v) +
        ";expires=" + (new Date(e * 1000)).toGMTString() +
        ";path=/" + ";domain=www.yahoo.com";
};

  如果我们不需要支持Netscape3和IE4,我们能通过对象字面量来实现的更加优雅。

var _yc = {
    get: function (n) {
        var v = '',
            c = ' ' + document.cookie + ';',
            s = c.indexOf((' ' + n + '='));
        if (s >= 0) {
            s += n.length + 2;
            v = unescape(c.substring(s, c.indexOf(';', s)));
        }
        return v;
    },
    set: function (n,v,e) {
        document.cookie = n + "=" + escape(v) +
            ";expires=" + (new Date(e * 1000)).toGMTString() +
            ";path=/" + ";domain=www.yahoo.com";
    }
};

使用通用库

  此时对于处理cookies我们有几种方法。我们发现下一个事情是令人惊奇的,它是没有利用我们定义的方法的cookies处理方式代码。

var b,
    l = '',
    n = '0',
    y;
y = ' ' + document.cookie + ';';
if ((b = y.indexOf(' Y=v')) >= 0) {
    y = y.substring(b, y.indexOf(';', b)) + '&';
    if ((b = y.indexOf('l=')) >= 0) {
        l = y.substring(b + 2, y.indexOf('&', b));
        if ((b = y.indexOf('n=')) >= 0)
            n = y.substring(b + 2, y.indexOf('&', b));
    }
}

  它甚至复制了我们早前看到的同样技术。很有可能两块代码都改写自同一个有缺点的原稿。我们可以利用我们最近的工作来改进它:

var l = '',
    n = '0',
    y = _yc.get('Y') + '&',
    b = y.indexOf('l=');
if (b >= 0) {
    l = y.substring(b + 2, y.indexOf('&', b));
    b = y.indexOf('n=');
    if (b >= 0) {
        n = y.substring(b + 2, y.indexOf('&', b));
    }
}

  代码重用是软件工程的圣杯。我们可以想象通过最先进的技术避免大量的必需的手工工作来得到高效率。这里我们发现一种失败,使用一种方法需要在相邻需要它的地方进行定义。

  软件的体系结构倾向于反映生产他们的组织结构。在这种情况下,我们看到一个组织由于缺乏流程的连通意识而导致的明显低效的证据。风格的应用是吹毛求疵的,因为如果我们理解这几条是什么才有可能正确的一起使用这几条。

原文:Douglas CrockfordThe Elements of JavaScript Style Part One

  我们使用习惯用法可以使我们的意图更加的清晰和简洁。

使用==时,当心强制转换

  考虑下面函数:

function gw(f) {
 if (d.w.sv.checked == true) {
  zv = 'on';
 } else {
  zv = 'off';
 }
 procframe.location.replace("http://b.www.yahoo.com/module/wtr_tr.php?p=" +
 escape(f.p.value) + "&sv=" + zv);
 return false;
}

  ==运算符不应该被用着和true比较值,因为它要执行强制转换。如果我们想确定d.w.sv.checked是否是布尔值
true,我们必须用===运算符。如果我们仅在意一个值是真实存在的不是假的,最好不要用相等运算符。

  例如,由于强制转换:1 == true是真,1 === true是假。==运算符隐藏了类型错误。

使用?:运算符选择两值之一

  if语句通常被用来从两个值中选择一个。这应该是三元操作符?:最适合的。

zv = d.w.sv.checked ? 'on' : 'off';

绝不使用隐含的全局变量

  变量zv不是作为一个var或函数参数来声明的,所以它是一个隐式的全局变量。如果这个页面的另一个函数使用了同样名字的全局变量,则可能得到一个失败的结果。这样的臭虫(bug)是非常难以发现,却很容易避免。这个例子中,我们既可以声明zv为一个var,也可以发现它仅仅被使用过一次而整个去掉它。

function gw(f) {
 procframe.location.replace("http://b.www.yahoo.com/module/wtr_tr.php?p=" +
 escape(f.p.value) + "&sv=" + d.w.sv.checked ? 'on' : 'off');
 return false;
}

绝不使用?:运算符选择两种行为之一

  我们常质疑那些返回一个常量的函数,但这有时是在浏览器环境下所必需的。

  下面我们看一个不正确使用?:运算符的例子。它常被用于在两个任务间选择。

function u(o, z) {
 var em = o.id.substring(1);
 var p = d.getElementById('e' + em);
 if (p) {
  (z == 0) ? p.style.backgroundColor = '#fff' :
  p.style.backgroundColor = '#989898';
 }
 p = d.getElementById('e' + (em - 1));
 if (p) {
  (z == 0) ? p.style.backgroundColor = '#fff' :
  p.style.backgroundColor = '#989898';
 }
}

Z的判断是模糊不清的。Z正好等于0时我们选择#fff颜色,那么,Z不等于时?如上所述似乎指明的是前者,但它实际上是后者。在这个例子中幸运的是,我们大概想要的就是后者,所以它不是技术上错误(这次)。但是在文体上只糟糕的。

  我们可以用if代替?:,但碰巧的是这些值对应的是同一个左值(lvalue),所以我们无需if就可以改正这个错误。

function u(o, z) {
 var em = o.id.substring(1),
 p = d.getElementById('e' + em);
 if (p) {
  p.style.backgroundColor = z ? '#fff' : '#989898';
 }
 p = d.getElementById('e' + (em - 1));
 if (p) {
  p.style.backgroundColor = z ? '#fff' : '#989898';
 }
}

使用||运算符指定一个默认值

  事件处理程序依赖于浏览器。理想情况下,应用程序应该通过公共库隔绝对浏览器的依赖。当没有这样的库时,就会有些函数发生如下情况:

function md(e) {
 (window.event) ? ev = window.event : ev = e;
 (ev.target) ? sr = ev.target : sr = ev.srcElement;
 if (ev && sr && sr.id == "fp" || sr.id == "sb") st = 1;
 if (sr.className.indexOf("pllist") < 0 && sr.className != "more" &&
  sr.className != "plinkc" && sr.tagName != "scrollbar " &&
  _toClose && _toCloseNorgie) {
  d.getElementById(_toClose).innerHTML = "";
  _toClose = "";
  _toCloseNorgie.parentNode.className = '';
  _toCloseNorgie = '';
 }
}

  一些浏览器把事件对象作为一个参数传给事件管理程序。微软选用把事件对象放入到一个全局的事件变量中。在Javascript中,全局变量是全局对象的成员。在浏览器中,全局对象始终包含一个window对象成员,其值是全局对象。当测试一个变量是否存在时,通过window访问全局变量是避免未定义变量错误的一种方法。无论如何,做这样的测试不应该是必要的。

  我们能通过问它是否是另外一种,来代替首先判断是否是微软事件。

ev = e || event;

  我们用||(默认)运算符。如果e是真,我们将有它的值,但是如果e是假,则我们将用event

  在下一个语句,我们又用||运算符去确定sr是哪个值。

  我们应该用var去声明evsr来避免全局冲突:

function md(e) {
 var ev = e || event,
  sr = ev.target || ev.srcElement;
 if (sr && (sr.id == 'fp' || sr.id == 'sb')) {
  st = 1;
 }
 if (sr.className.indexOf('pllist') < 0 && sr.className != 'more' &&
 sr.className != 'plinkc' && sr.tagName != 'scrollbar ' &&
 _toClose && _toCloseNorgie) {
  d.getElementById(_toClose).innerHTML = '';
  _toClose = '';
  _toCloseNorgie.parentNode.className = '';
  _toCloseNorgie = '';
 }
}

全局变量是魔鬼

  下面我们看到另一个时间处理程序。正如你所料,它重复像前面一样破坏风格。

function kd(e) {
 (window.event) ? ev = window.event : ev = e;
 (ev.target) ? el = ev.target : el = ev.srcElement;
 if (ev && el) {
  code = ev.keyCode;
  id = el.id;
 } else {
  return;
 }
 ctn = lt.id.substring(1);
 if (code == 13) {
  return;
 } else if ((code == 191 || code == 222) && id != 'fp') {
  _ffs = 1;
  gk = 0;
 } else if ((code < 31 || code > 41) &&
 (code < 16 || code > 18) && code != 9 && code != 8 ) {
  gk = 1;
 } else {
  gk = 0;
 }
 if (!_ffs && (id == 'fp' || id == 'st')) {
  if (code == 9) {
   if (box.value == '' || (box.value != '' && (at == 1 || ev.shiftKey))) {
    mt(ctn);
   } else if (id == 'st' && box.value != '' && at == 0) {
    at = 1;
    mt(ctn);
   }
  } else if (id == 'fp' && gk == 0 &&  (box.value == '' && st == 0)
    && !ev.shiftKey && !ev.ctrlKey && !ev.altKey) {
    d.getElementById('mk').focus();
    d.getElementById('mk').blur();
  } else if (gk == 1) {
  at = 0;
 }
 } else if ((id == 'mk2' && box.value != '' && ev.shiftKey && code == 9) ||
   (id == 'm6' && !ev.shiftKey && code == 9)){
    d.getElementById('mk').focus();
   } else if (!_ffs && gk == 1 && el.type != 'text' && !ev.ctrlKey && !ev.altKey){
    box.value = '';
    box.focus();
  }
}
function mt(ctn) {
 if ((ev && !ev.ctrlKey && !ev.altKey) || !ev) {
  if (ev.shiftKey){
   nextTab = parseInt(ctn) - 1;
  } else {
   nextTab = parseInt(ctn) + 1;
  }
  if (nextTab == 0) {
   d.getElementById('mk').focus();
  } else if (nextTab < 8 ) {
   t(d.getElementById('v' + nextTab));
  } else {
   return;
  }
 }
}

  有意思的是它有一个同伴函数mt,它仅被kd调用。mt被传给一个参数ctn,但kdmt之间的通讯大部分是通过全局变量。

使用内部函数避免全局变量

  我们可以通过增加传递给mt的参数数量来除掉所有的全局变量。但代替方案,我们将使mt变成kd的内部函数。作为一个内部函数,mt能访问kd的所有变量。

function kd(e) {
    var ev = e || event,
        el = ev.target || ev.srcElement,
        cnt,
        code = ev.keyCode,
        gk,
        id = el.id,
        ctn = lt.id.substring(1);

    function mt() {
        var nextTab;
        if (!ev.ctrlKey && !ev.altKey) {
            nextTab = parseInt(ctn) + ev.shiftKey ? -1 : 1;
            if (!nextTab) {
                d.getElementById('mk').focus();
            } else if (nextTab < 8 ) {
                t(d.getElementById('v' + nextTab));
            }
        }
    }

    if (code == 13) {
        return;
    } else if ((code == 191 || code == 222) && id != 'fp') {
        _ffs = 1;
        gk = 0;
    } else if ((code < 31 || code > 41) &&
            (code < 16 || code > 18) && code != 9 && code != 8 ) {
        gk = 1;
    } else {
        gk = 0;
    }
    if (!_ffs && (id == 'fp' || id == 'st')) {
        if (code == 9) {
            if (box.value == '' ||
                    (box.value != '' && (at == 1 || ev.shiftKey))) {
                mt();
            } else if (id == 'st' && box.value != '' && at == 0) {
                at = 1;
                mt();
            }
        } else if (id == 'fp' && gk == 0 && (box.value == '' && st == 0) &&
                !ev.shiftKey && !ev.ctrlKey && !ev.altKey) {
            d.getElementById('mk').focus();
            d.getElementById('mk').blur();
        } else if (gk == 1) {
            at = 0;
        }
    } else if ((id == 'mk2' && box.value != '' && ev.shiftKey && code == 9) ||
            (id == 'm6' && !ev.shiftKey && code == 9)){
        d.getElementById('mk').focus();
    } else if (!_ffs && gk == 1 && el.type != 'text' && !ev.ctrlKey &&
            !ev.altKey) {
        box.value = '';
        box.focus();
    }
}

  在函数kd中,从两个地方调用函数mt。通过使它成为一个内部函数,我们能有效的减少kd所用到的全局变量的数目,这将降低了干扰其他组件的可能性。kd依旧是一个烂摊子,但它现在不是一无是处的烂摊子。

原文:Douglas CrockfordThe Elements of JavaScript Style Part Two: Idioms

本文转自 Part One
Part Two 作者:Kaven

技术文章 ,