Javascript风格要素 Ⅰ

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

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

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

1<script language=javascript> 2<!-- 3lck='', 4sss=1127143538, 5ylp='p.gif?t=1127143538&_ylp=A0Je5ipy2C5D54AAwVX1cSkA', 6_lcs=''; 7--> 8</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会创建一个新的全局变量来替代产生的错误。事后看 来,这是一个错误。即使当他们是一个标准错误,这是避免错误的最好办法。我们应该明确的声明变量。它花费我们四个字符,但是它正是要做的正确的事。

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

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

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

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

1function yg_cookie(){ 2 this.get = function(n){ 3 var s, e, v = '', c = ' ' + document.cookie + ';'; 4 if ((s = c.indexOf((' ' + n + '='))) >= 0) { 5 if ((e = c.indexOf(';', s)) == -1) 6 e = c.length; 7 s += n.length + 2; 8 v = unescape(c.substring(s, e)); 9 } 10 return (v); 11 } 12 this.set = function(n, v, e){ 13 document.cookie = n + "=" + escape(v) + 14 ";expires=" + 15 (new Date(e * 1000)).toGMTString() + 16 ";path=/" + 17 ";domain=www.yahoo.com"; 18 } 19} 20var _yc = new yg_cookie();

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

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

写成

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

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

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

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

避免赋值表达式

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

1this.get = function(n){ 2 var v = '', c = ' ' + document.cookie + ';', s = c.indexOf((' ' + n + '=')), e = c.indexOf(';', s); 3 if (s >= 0) { 4 if (e == -1) { 5 e = c.length; 6 } 7 s += n.length + 2; 8 v = unescape(c.substring(s, e)); 9 } 10 return (v); 11}

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

使用对象参数

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

1function yg_cookie(){ 2 this.get = function(n){ 3 var v = '', c = ' ' + document.cookie + ';', s = c.indexOf((' ' + n + '=')); 4 if (s >= 0) { 5 s += n.length + 2; 6 v = unescape(c.substring(s, c.indexOf(';', s))); 7 } 8 return v; 9 }; 10 this.set = function(n, v, e){ 11 document.cookie = n + "=" + escape(v) + 12 ";expires=" + 13 (new Date(e * 1000)).toGMTString() + 14 ";path=/" + 15 ";domain=www.yahoo.com"; 16 }; 17} 18var _yc = new yg_cookie();

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

1var _yc = new Object(); 2_yc.get = function(n){ 3 var v = '', c = ' ' + document.cookie + ';', s = c.indexOf((' ' + n + '=')); 4 if (s >= 0) { 5 s += n.length + 2; 6 v = unescape(c.substring(s, c.indexOf(';', s))); 7 } 8 return v; 9}; 10_yc.set = function(n, v, e){ 11 document.cookie = n + "=" + escape(v) + 12 ";expires=" + 13 (new Date(e * 1000)).toGMTString() + 14 ";path=/" + 15 ";domain=www.yahoo.com"; 16};

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

1var _yc = { 2 get: function(n){ 3 var v = '', c = ' ' + document.cookie + ';', s = c.indexOf((' ' + n + '=')); 4 if (s >= 0) { 5 s += n.length + 2; 6 v = unescape(c.substring(s, c.indexOf(';', s))); 7 } 8 return v; 9 }, 10 set: function(n, v, e){ 11 document.cookie = n + "=" + escape(v) + 12 ";expires=" + 13 (new Date(e * 1000)).toGMTString() + 14 ";path=/" + 15 ";domain=www.yahoo.com"; 16 } 17};

使用通用库

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

1var b, 2l = '', 3n = '0', 4y; 5y = ' ' + document.cookie + ';'; 6if ((b = y.indexOf(' Y=v')) >= 0) { 7 y = y.substring(b, y.indexOf(';', b)) + '&'; 8 if ((b = y.indexOf('l=')) >= 0) { 9 l = y.substring(b + 2, y.indexOf('&', b)); 10 if ((b = y.indexOf('n=')) >= 0) 11 n = y.substring(b + 2, y.indexOf('&', b)); 12 } 13}

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

1var l = '', 2n = '0', 3y = _yc.get('Y') + '&', 4b = y.indexOf('l='); 5if (b >= 0) { 6 l = y.substring(b + 2, y.indexOf('&', b)); 7 b = y.indexOf('n='); 8 if (b >= 0) { 9 n = y.substring(b + 2, y.indexOf('&', b)); 10 } 11}

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

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

作者:Douglas Crockford
原文:The Elements of JavaScript Style Part One
翻译:秦歌随网之舞

点赞
收藏

评论区

加载中...

相关推荐

MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1

文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s

Oracle 分组与拼接字符串同时使用

SELECTT.,ROWNUMIDFROM(SELECTT.EMPLID,T.NAME,T.BU,T.REALDEPART,T.FORMATDATE,SUM(T.S0)S0,MAX(UPDATETIME)CREATETIME,LISTAGG(TOCHAR(

皕杰报表之UUID

​在我们用皕杰报表工具设计填报报表时,如何在新增行里自动增加id呢?能新增整数排序id吗?目前可以在新增行里自动增加id,但只能用uuid函数增加UUID编码,不能新增整数排序id。uuid函数说明:获取一个UUID,可以在填报表中用来创建数据ID语法:uuid()或uuid(sep)参数说明:sep布尔值,生成的uuid中是否包含分隔符'',缺省为

手写Java HashMap源码

HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程22

一篇文章带你了解JavaScript日期

日期对象允许您使用日期(年、月、日、小时、分钟、秒和毫秒)。一、JavaScript的日期格式一个JavaScript日期可以写为一个字符串:ThuFeb02201909:59:51GMT0800(中国标准时间)或者是一个数字:1486000791164写数字的日期,指定的毫秒数自1970年1月1日00:00:00到现在。1\.显示日期使用

Twitter的分布式自增ID算法snowflake (Java版)

概述分布式系统中,有一些需要使用全局唯一ID的场景,这种时候为了防止ID冲突可以使用36位的UUID,但是UUID有一些缺点,首先他相对比较长,另外UUID一般是无序的。有些时候我们希望能使用一种简单一些的ID,并且希望ID能够按照时间有序生成。而twitter的snowflake解决了这种需求,最初Twitter把存储系统从MySQL迁移

Javascript风格要素 Ⅰ - HelloWorld