第一章 基本的格式化
-
縮進層級:推薦 tab:4;
-
換行:在運算符后面換行,第二行追加兩個縮進;
// Good: Break after operator, following line indented two levels callAFunction(document, element, window, "some string value", true, 123, navigator); // Bad: Following line indented only one level callAFunction(document, element, window, "some string value", true, 123, navigator); // Bad: Break before operator callAFunction(document, element, window, "some string value", true, 123 , navigator);
-
添加空行:在方法之間; 在方法的局部變量和第一條語句之間; 在多行或單行注釋之前; 在方法內的邏輯片段插入空行提高可讀性(比如for, if);
// good if (wl && wl.length) { for (i = 0, l = wl.length; i < l; ++i) { p = wl[i]; type = Y.Lang.type(r[p]); if (s.hasOwnProperty(p)) { if (merge && type == 'object') { Y.mix(r[p], s[p]); } else if (ov || !(p in r)) { r[p] = s[p]; } } } }
-
常量:采用大寫字符加下划線的方式(var MAX_COUNT);
var MAX_COUNT = 10; var URL = "http://www.nczonline.net/"; if (count < MAX_COUNT) { doSomething(); }
-
不推薦顯示的構建數組和對象(采用new的方式);
- 字符串:推薦使用雙引號;
// Good var name = "Nicholas"; // Bad: Single quotes var name = 'Nicholas'; // Bad: Wrapping to second line var longString = "Here's the story, of a man \ named Brady.";
-
不要使用“\”來連接字符串,推薦使用“+”;
第二章 注釋
- 注釋:注釋前面空一行,與下一行對其; 程序代碼比較明了的時候,不推薦添加注釋; 可能被認為錯誤的代碼可加注釋提醒不要隨意修改;
/* *注意前面的空格(不好) * 注意前面的空格(好) */
第三章 語句和表達式
- 塊語句間的空格,推薦使用下面這種
if (condition) { doSomething(); }
- 不要使用 for in 來循環數組,因為 index 是字符串形式,而不是數字形式,容易出現問題。可以用來循環對象;
第四章 變量、函數和運算符
- 嚴格模式:只在需要的地方添加,盡量不要全局使用,除非你已經明確都要嚴格模式。
// 推薦使用方式 function fn() { "use strict"; //代碼 } //或者 (function() { "use strict"; function fn1() { } function fn2() { } })();
-
相等:推薦使用“===” 和 “!==”這樣可以避免轉換,出現潛在的風險;
-
盡量不適用 eval , Function, 以及不要給 setTimeout,setInterval 傳入字符串;
第五章 UI的松耦合
-
避免使用css 表達式 expression;
-
用 js 修改樣式的時候,采用添加或者移除類名的方式,而不是直接使用.style.color .style.cssText;
// Bad element.style.color = "red"; element.style.left = "10px"; element.style.top = "100px"; element.style.visibility = "visible"; element.style.cssText = "color: red; left: 10px; top: 100px; visibility: hidden"; //樣式 .reveal { color: red; left: 10px; top: 100px; visibility: visible; } // Good - Native element.className += " reveal"; // Good - HTML5 element.classList.add("reveal"); // Good - YUI Y.one(element).addClass("reveal"); // Good - jQuery $(element).addClass("reveal"); // Good - Dojo dojo.addClass(element, "reveal");
-
在HTML中不要使用javascript;比如嵌入在HTML中的onClick
<!-- Bad --> <button onclick="doSomething()" id="action-btn">Click Me</button>
-
將HTML從JavaScript中 抽離。
// Bad
var div = document.getElemenetById("my-div"); div.innerHTML = "<h3>Erroe</h3>";
- 解決辦法有三種:1、從服務器加載;
function loadDialog(name, oncomplete) { var xhr = new XMLHttpRequest(); xhr.open("get", "/js/dialog/" + name, true); xhr.onreadystatechange = function() { if (xhr.readyState == 4 && xhr.status == 200) { var div = document.getElementById("dlg-holder"); div.innerHTML = xhr.responseText; oncomplete(); } else { // handle error } }; xhr.send(null); }
- 2、使用簡單客戶端模板;
// 模板 <li><a href="%s">%s</a></li> function sprintf(text) { var i=1, args=arguments; return text.replace(/%s/g, function() { return (i < args.length) ? args[i++] : ""; }); } // usage var result = sprintf(templateText, "/item/4", "Fourth item");
- 3、復雜客戶端模板; 比如使用 Handlebars
// 模板放在 script 中 <script type="text/x-handlebars-template" id="list-item"> <li><a href="{{url}}">{{text}}</a></li> </script> function addItem(url, text) { var mylist = document.getElementById("mylist"), script = document.getElementById("list-item"), templateText = script.text, template = Handlebars.compile(script.text), div = document.createElement("div"), result; result = template({ text: text, url: url }); div.innerHTML = result; list.appendChild(div.firstChild); } // 使用 addItem("/item/4", "Fourth item");
第六章 避免使用全局變量
-
避免使用全局變量,存在問題:命名沖突;代碼的脆弱性;難以測試
-
意外的全局變量:忘了var; 解決辦法: 使用 JSLint 或者 JSHint ;
-
單全局變量: 比如YUI定義了一個YUI全局變量; jQuery 定義了兩個全局變量,$ 和 jQuery;
-
命名空間: 比如 YUI.event 下的所有方法都是和事件相關的。
-
模塊: AMD模塊 和 YUI 模塊;
// AMD define("my-books", [ "dependency1", "dependency2" ], function(dependency1, dependency2) { var Books = {}; Books.MaintainableJavaScript = { author: "Nicholas C. Zakas" }; return Books; });
- 零全局變量:使用前提是內部代碼與外部代碼無聯系。
(function(win) { var doc = win.document; })(window)
第七章 事件處理
- 事件處理典型用法
// 不好的 function handleClick(event) { var popup = document.getElementById("popup"); popup.style.left = event.clientX + "px"; popup.style.top = event.clientY + "px"; popup.className = "reveal"; } // addListener() from Chapter 7 addListener(element, "click", handleClick);
- 規則1: 隔離應用邏輯
// 好的 - separate application logic var MyApplication = { handleClick: function(event) { this.showPopup(event); }, showPopup: function(event) { var popup = document.getElementById("popup"); popup.style.left = event.clientX + "px"; popup.style.top = event.clientY + "px"; popup.className = "reveal"; } }; addListener(element, "click", function(event) { MyApplication.handleClick(event); });
- 規則2: 不要分發事件對象;上面存在的問題是event 事件被無節制分發。從匿名事件處理函數到另一個函數,再到另一個函數。最佳方法: 讓事件處理程序使用event對象處理事件,然后拿到所有需要的數據傳給應用邏輯。
// Good var MyApplication = { handleClick: function(event) { this.showPopup(event.clientX, event.clientY); }, showPopup: function(x, y) { var popup = document.getElementById("popup"); popup.style.left = x + "px"; popup.style.top = y + "px"; popup.className = "reveal"; } }; addListener(element, "click", function(event) { MyApplication.handleClick(event); // this is okay });
- 當處理事件時候,最好讓事件處理程序成為接觸到event對象的唯一的函數。
// Good var MyApplication = { handleClick: function(event) { // assume DOM Level 2 events support event.preventDefault(); event.stopPropagation(); // pass to application logic this.showPopup(event.clientX, event.clientY); }, showPopup: function(x, y) { var popup = document.getElementById("popup"); popup.style.left = x + "px"; popup.style.top = y + "px"; popup.className = "reveal"; } }; addListener(element, "click", function(event) { MyApplication.handleClick(event); // this is okay });
第八章 避免 “空比較“
- 下面是不好的示范,你很多都與null不相等,包括數字,字符串,對象等,從而帶來隱藏的錯誤。只有當期待的值是null的時候,可以和null進行比較。
var Controller = { process: function(items) { if (items !== null) { // Bad items.sort(); items.forEach(function(item) { // do something }); } } };
- 檢測基本類型使用typeof, 引用類型使用instanceof, 還有一種鴨式辨型,采用對象特有的方法或者屬性是否存在來判斷,例如array.sort(), 正則的test方法。通用的方法是,其他類型類似:
function isArray(value) { return Object.prototype.toString.call(value) === "[object Array]"; }
- 檢測屬性,判斷屬性最好的辦法是用in
// 不好,當屬性值是0, "", false, null或者 undefined if (object[propertyName]) { // do something } // Bad: Compare against null if (object[propertyName] != null) { // do something } // Bad: Compare against undefined if (object[propertyName] != undefined) { / do something } var object = { count: 0, related: null }; // Good if ("count" in object) { // this executes } if ("related" in object) { // this executes } //當你只想檢查實例對象的某個屬性是否存在的時候使用hasOwnProperty() // Good for all non-DOM objects if (object.hasOwnProperty("related")) { //this executes } // Good when you're not sure if ("hasOwnProperty" in object && object.hasOwnProperty("related")) { //this executes }
第九章 將配置數據從代碼中分離出來
- 什么是配置數據? 就是JavaScript代碼中有可能發生改變的。比如:url, 需要展現給用戶的字符串, 重復的值, 設置, 任何可能發生變化的值。下面是不好的實例,其中的 invalid value, href的值, selcted 都是配置數據。
// Configuration data embedded in code function validate(value) { if (!value) { alert("Invalid value"); location.href = "/errors/invalid.php"; } } function toggleSelected(element) { if (hasClass(element, "selected")) { removeClass(element, "selected"); } else { addClass(element, "selected"); } }
- 抽離配置數據
// Configuration data externalized var config = { MSG_INVALID_VALUE: "Invalid value", URL_INVALID: "/errors/invalid.php", CSS_SELECTED: "selected" }; function validate(value) { if (!value) { alert(config.MSG_INVALID_VALUE); location.href = config.URL_INVALID; } } function toggleSelected(element) { if (hasClass(element, config.CSS_SELECTED)) { removeClass(element, config.CSS_SELECTED); } else { addClass(element, config.CSS_SELECTED); } }
第十章 拋出自定義錯誤
- 推薦在錯誤消息中包含函數名稱,以及函數失敗的原因。
function getDivs(element) { if (element && element.getElementsByTagName) { return element.getElementsByTagName("div"); } else { throw new Error("getDivs(): Argument must be a DOM element."); } }
- 何時拋出錯誤? 辨別代碼中哪些部分在特定的情況下最優可能導致失敗,並只在哪些地方拋出錯誤才是關鍵所在。我們的目的不是防止錯誤,而是錯誤發生的時候更加容易地調試。
// 不好的,太多檢查 function addClass(element, className) { if (!element || typeof element.className != "string") { throw new Error("addClass(): First argument must be a DOM element."); } if (typeof className != "string") { throw new Error("addClass(): Second argument must be a string."); } element.className += " " + className; } // 好的寫法 第二個參數是null 或者一個數字或者一個布爾值時是不會拋出錯誤的 function addClass(element, className) { if (!element || typeof element.className != "string") { throw new Error("addClass(): First argument must be a DOM element."); } element.className += " " + className; }
- 使用try catch 語句,當在try中 發生一個錯誤,會立即跳到catch語句,傳入一個錯誤對象。還可以增加一個finally 模塊,不管錯誤發不發生都會執行。
try { somethingThatMightCauseAnError(); } catch (ex) { handleError(ex); } finally { continueDoingOtherStuff(); }
第十一章 不是你的對象不要動
- 以下這些對象不要嘗試去修改他們,因為他們已經存在了: 原生對象(Object, Array), DOM對象(document), 瀏覽器對象模型(BOM)對象(window), 類庫的對象。 對待他們的原則是:不覆蓋方法; 不新增方法; 不刪除方法; 下面是一些不好的示例:
document.getElementById = function() { return null; // talk about confusing }; document._originalGetElementById = document.getElementById; document.getElementById = function(id) { if (id == "window") { return window; } else { return document._originalGetElementById(id); } }; document.sayImAwesome = function() { alert("You're awesome."); }; Array.prototype.reverseSort = function() { return this.sort().reverse(); }; YUI.doSomething = function() { // code }; document.getElementById = null;
更好的途徑:有三種,見代碼
//基於對象的繼承 var person = { name: "Nicholas", sayName: function() { alert(this.name); } }; var myPerson = Object.create(person); myPerson.sayName(); // pops up "Nicholas" //基於類型的繼承 function MyError(message) { this.message = message; } MyError.prototype = new Error(); var error = new MyError("Something bad happened."); console.log(error instanceof Error); // true console.log(error instanceof MyError); // true //門面模式 與適配器唯一不同是 其創建新的接口,后者實現已經存在的接口 function DOMWrapper(element) { this.element = element; } DOMWrapper.prototype.addClass = function(className) { this.element.className += " " + className; }; DOMWrapper.prototype.remove = function() { this.element.parentNode.removeChild(this.element); }; // Usage var wrapper = new DOMWrapper(document.getElementById("my-div")); // add a CSS class wrapper.addClass("selected"); // remove the element wrapper.remove();
第十二章 瀏覽器嗅探
- User-agent 檢測
// Bad if (navigator.userAgent.indexOf("MSIE") > -1) { // it's Internet Explorer } else { // it's not } // good 不要試圖檢測IE 9及其高版本,而應該只檢測IE8以及之前的 if (isInternetExplorer8OrEarlier) { // handle IE8 and earlier } else { // handle all other browsers }
- 特性檢測,推薦使用特性檢測
// Bad if (navigator.userAgent.indexOf("MSIE 7") > -1) { // do something } // Good if (document.getElementById) { // do something }
- 避免特性推斷
// Bad - uses feature inference // 不能從一個特性推斷出另一個特性是否存在 function getById (id) { var element = null; if (document.getElementsByTagName) { // DOM element = document.getElementById(id); } else if (window.ActiveXObject) { // IE element = document.all[id]; } else { // Netscape <= 4 element = document.layers[id]; } return element; }
- 避免瀏覽器推斷,你不能根據某種方法存在就判定是某一種瀏覽器
// Bad if (document.all) { // IE id = document.uniqueID; } else { id = Math.random(); } var isIE = !!document.all; var isIE = !!document.all && document.uniqueID;
后面的章節是另一部分,比如文件目錄,自動化啊等,不好總結。
如果覺得好,請點擊下方的推薦噢,感謝您的閱讀!