Javascript基础知识篇(6): 高质量开发准则(上)
很多从事web开发的项目都离不开Javascript,我个人也在目前的公司中用到了很多,但是感觉还不是很到位,尤其是对Javascript的开发规范上。可能有些朋友要说,我实现一个简单的的功能(如两个数相加),需要遵守什么规范吗?我回答:需要。还有的会问:我的项目进度非常紧张,我有必要按照你说的规范一步一步来吗?我还是回答:需要。为什么呢?我认为与其在项目开发完毕的维护过程中,反复的调试还不如提前遵守开发规范,养成良好的编码习惯。结合我个人一直以来的职业习惯,我总结了以下几点,作为抛砖引玉。保持一致的编码风格。具体做法:
(1).函数都必须拥有函数名(匿名函数除外),并以tab作为函数内容的缩进,函数体应包含在{}中(最好前{在函数名后)。
function sayHello() {//注意大括号的位置 alert("Hello, Miracle"); return true; }
(2).函数体即使只有一句话(switch除外),也建议用{}封闭起来。这样有利于以后需求变更和维护,以防止新手忘记添加{}。
if(x == y) { alert("We have a match!"); } else { alert("We have a problem!"); }
switch (prompt("What is your favorite fruit?", "banana")) { case "banana": alert("Well done. Bananas are full of potassium."); break; case "mango": alert("Exotic and tasty!"); break; default: alert("That's not to my taste, but keep it up to get your 5 days."); break; }
(3).注重大小写。一般准则如下:
a.大多数的变量和函数均以小写开头,采用驼峰法进行书写。
b.构造函数和类(实质是函数)均以大写开头。
c.所有的常量都是由大写组成并以"_"相连。
// Calendar构造函数 var Calendar = function() { // 大写标识常量 var DAYS_IN_ONE_WEEK = 7; // 小写代表函数及变量 var count; function add() { } // 变量及函数遵守驼峰法书写 var currentDate; function updateCurrentDate() { } }
(4).使用有描述语义的变量,避免使用"魔幻数"(即变量直接由一些常量数字计算而成)。
// currentDate代表"当前日期",userName代表"用户名" var currentDate; function setCurrentDate() { } var userName; function updateUserName() { } // daysInAYear和seatsOnThePlane均为"魔幻数" var daysInAYear = 52 * 7; var seatsOnThePlane = 25 * 6; //改进方案 var WEEKS_IN_A_YEAR = 52; var DAYS_IN_A_WEEK = 7; var daysInAYear = WEEKS_IN_A_YEAR * DAYS_IN_A_WEEK; var SEAT_ROWS_ON_PLANE = 25; var SEATS_PER_ROW = 6; var seatsOnThePlane = SEAT_ROWS_ON_PLANE * SEATS_PER_ROW;
(5).函数功能尽量单一,不要融入太多"复杂"逻辑,尽量按职责拆分成对应的子函数。
// 反馈用户对水果的爱好程度 function feedbackOnUsersFavoriteFruit() { // 用户喜欢的水果 var favoriteFruit = prompt("What is your favorite fruit?", "None"); var score = 0; // 根据喜欢的水果计算得分 switch (favoriteFruit.toLowerCase()) { case "banana": score = 6; break; case "apple": score = 4; break; default: break; } //根据分数来统计结果 if (score > 5) { alert("You picked one of my favorites too!"); } else if (score > 0) { alert("Credit for choosing a fruit, at least!"); } else { alert("Not sure about your choice of fruit!"); } }
从上面的函数我们看出:包含获取喜欢水果和计算得分两项职责(实际项目中很可能是多项职责),我们在这里加以拆分。
// 用户喜欢的水果 function getFavoriteFruit() { return prompt("What is your favorite fruit?", "None"); } // 根据喜欢的水果计算得分 function getFruitScore(fruit) { var score = 0; switch (fruit.toLowerCase()) { case "banana": score = 6; break; case "apple": score = 4; break; default: break; } return score; } //根据分数来统计结果 function getMessageByScore(score) { var message = ""; if (score > 5) { message = "You picked one of my favorites too!"; } else if (score > 0) { message = "Credit for choosing a fruit, at least!"; } else { message = "Not sure about your choice of fruit!"; } return message; } //最开始的函数 function feedbackOnUsersFavoriteFruit() { var favoriteFruit = getFavoriteFruit(); var score = getFruitScore(favoriteFruit); alert(getMessageByScore(score)); }
(6).习惯利用scriptDoc为函数添加注释。包含项目文件描述(包含功能、作者及版本等),函数功能描述(包含参数,返回值,示例程序)。
/** * @projectDescription 该文件用于日期对象操作 * * @author Miracle He hmiinyu@sina.com * @version 1.0 */ /** 传入一个数并计算它的平方并返回四舍五入后的整数结果 * * Examples: * square(2.5); => 6 * square(2); => 4 * @param {Float} 传入计算的数 * @return {Integer} 返回四舍五入后的整数结果 */ function square(number) { return Math.round(number * number); }
(7).习惯使用"//TODO:[功能描述]"标记未完成的功能。以便于团队其他成员进行添加。
function checkPasswordStrength(password) { // TODO: 请完成密码强度检查 return true; }
(8).避免编写解决"不存在"问题的代码。即在项目开发中仅仅只编写与你问题相关的代码,而不是刻意添加与问题不相关的代码。如果你认为所做的代码能达到通用的层次,则建议将它封装到工具类中,而不是散列到各个业务文件中。这样可以更好的维护代码和提高重用性。举个例子,有时做项目经常碰到在前端页面获取MasterPage或者UserControl中某一个子控件时,在外部文件中不能使用$("#<%=id.ClientID%>")的方式来获取控件,因此我封装了一个函数并放到oa.lib.js中(oa是项目名,可自定义)。
/*DOM*/ oa.prototype.dom = { //根据ID获取对应的元素 get: function (id, tag) { if (tag == undefined) return $("#" + id); switch (tag.toLowerCase()) { case "drop": return $("select[name$=" + id + "]"); case "radio": return $("input[name$=" + id + "]"); case "check": case "table": return $("table[id$=" + id + "]"); case "input": return $("input[id$=" + id + "]"); case "link": return $("a[id$=" + id + "]"); } }, //获取一组ID指定的元素 gets: function (ids, tag) { var arr = ids; //ids可为数组或以逗号分隔的ID列表 if (!(ids instanceof Array)) { arr = ids.split(','); } var elems = oa.dom.get(arr[0], tag); for (var i = 1; i < arr.length; i++) { elems = elems.add(oa.dom.get(arr[i], tag)); } return elems; } };
dom = oa.dom;
而当用户调用时,就很简单了。
<!--html--> <asp:Content ID="Content3" ContentPlaceHolderID="content" runat="server"> <asp:TextBox ID="txtSubject" runat="server" Text="科目"></asp:TextBox> </asp:Content> <!--javascript--> dom.get("txtSubject", "input");
(9).保持Javascript(行为),HTML(结构),CSS(展现)分离,js和css以外部文件方式导入到html中。不要将三者混搭,这样极不专业也难以维护。我还是举个例子来说明。
<html xmlns="http://www.w3.org/1999/xhtml"> <head runat="server"> <title>*****</title> <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> <link href="Contents/styles/index.css" rel="stylesheet" type="text/css" /> <script src="Scripts/lib/jquery-1.7.1.min.js" type="text/javascript"></script> <script src="Scripts/plugins/jquery.include.js" type="text/javascript"></script> <script src="Scripts/import/index.js" type="text/javascript"></script> <link rel="shortcut icon" href="Contents/images/common/favicon.ico" type="image/x-icon" /> </head> <body> <form id="form1" runat="server"> <div class="top"> <iframe src="Shared/Header.aspx" width="100%" scrolling="no" marginwidth="0" marginheight="0" frameborder="0" height="85px" id="header"></iframe> </div> <div class="content"> <div class="side"> <iframe src="Shared/Menu.aspx" height="100%" frameborder="0" scrolling="auto" id="menu"> </iframe> </div> <div class="bar"> <iframe src="Shared/MenuBar.aspx" height="100%" frameborder="0" scrolling="auto" id="menubar"> </iframe> </div> <div class="main"> <iframe src="Shared/PageContainer.aspx" height="100%" frameborder="0" scrolling="auto" id="page"></iframe> </div> </div> <div class="bottom"> Copyright ? 2007 - 2012 ***. All Rights Reserved </div> </form> </body> </html>
然后是外部文件(js,css),我这里就不列出具体内容了。
(10).尽量编写有"防护性"的代码,即对你编写的代码保持很好的逻辑判断和异常处理。下面我们来看一个示例。
var hr = document.getElementById("horizontal-rule"); if (hr) { // 如果对象存在的前提下执行 hr.parentNode.removeChild(hr); }
当你对你的代码不太确认时,请添加异常处理,以更好的进行调试。
try { // 以下代码不合法将导致异常 document.getElementById("horizontal-rule").propertyName.toString(); } catch (error) { alert("An error occurred."); )
请注意,才这里catch中的参数error,包含异常产生的原因和类型等信息。
try { // varx 不是有效的关键字,因此将抛出语法错误异常 varx x = 0; } catch (error) { // 以下列举六种对应的错误 if (error instanceOf TypeError) { // 类型错误... } else if (error instanceOf SyntaxError) { // 输出错误消息 alert(error.message); } else if (error instanceOf RangeError) { // 超界错误 } else if (error instanceOf EvalError) { // eval()执行错误 } else if (error instanceOf ReferenceError) { // 非法引用错误 } else if (error instanceOf URIError) { // URI解码错误 } }
有时为了满足业务需要,也可以自定义异常。
// 定义自定义异常 var domObjectNotFoundException = new Error("DOM object not found"); function getDOMObjectById(id) { var domObject = document.getElementById(id); if (!domObject) { // 当对象不存在时抛出错误 throw domObjectNotFoundException; } return domObject; } try { // 假定我们获取一个不存在的对象 getDOMObjectById("this-id-is-not-on-the-page").style.width = "100px"; } catch (error) { if (error instanceOf domObjectNotFoundException) { // 输出错误 alert(error.message); } else { // 做其他错误处理 } }
类似于很多面向对象的高级语言,你还能使用try/catch/finally形式,在finally在放置你必须执行的代码,不管是否产生异常。
try { getDOMObjectById("this-id-is-not-on-the-page").style.width = "100px"; } catch (error) { alert(error.message); } finally { alert("不管你的对错,我依然光顾!"); }
后续还有很多规范,我会逐步总结下来....