鬆耦合就是要求各層遵循「最少知識原則」,或者說是各層各司其職,不要越權:javascript
HTML:結構層css
CSS:表現層html
JS:行爲層java
對於各層的職能,有一句比較貼切的解釋:HTML是名詞(n),CSS是形容詞(adj)和副詞(adv),JS是動詞git
由於三層聯繫緊密,實際應用中很容易越權:github
儘可能不要用css表達式,若是非要用也應該把相應的代碼放在hack中,便於維護web
不要用ele.style.attr及ele.cssText,應該用操做類名代替編程
不要用onclick等屬性直接指定事件處理函數,應該用添加事件處理器方式代替設計模式
通常不要用<script>標籤直接嵌入js代碼,儘可能放在外部js文件中。固然,對於功能單一且不須要複用的代碼能夠直接嵌在HTML中,例如表單驗證代碼瀏覽器
不要直接用innerHTML硬編碼,能夠用如下3種方式代替:
用Ajax從服務端獲取HTML串,避免硬編碼
用簡單的客戶端模版,有2種實現方式:
用註釋攜帶模式串
用script標籤攜帶模式串,把type屬性設置爲瀏覽器沒法識別的值,還應該給script標籤設置id屬性以便於獲取模式串,例如:
<script type="text/x-my-template" id="list-item"> <li><a href="%s">%s</li> </script>
推薦使用這種方式,由於更容易獲取模式串
用複雜的客戶端模版,好比jade、ejs
P.S.由於html與css的解耦與js編程沒有關係,因此書中也沒有相應內容
命名衝突
代碼不健壯,函數須要的全部外部數據都應該用參數傳進來,而不要用全局變量傳參
難以測試,須要重建整個全局環境
不要用隱式全局變量方式聲明全局變量,建議全部變量聲明都帶上var
關鍵字
爲了不隱式全局變量,還應該開啓嚴格模式(」use strict」;),[IE10+]支持
用命名空間,提供一個避免命名空間衝突的方法:
// 頂級命名空間 var app = { /* * 建立/獲取子命名空間,支持鏈式調用 */ namespace: function(ns) { var parts = ns.split("."), object = this, i, len; for (i = 0, len = parts.length; i < len; i++) { if (!object[parts[i]]) { object[parts[i]] = {}; } object = object[parts[i]]; } return object; // 支持鏈式調用 } } // 測試 app.namespace("Consts").homepage = "http://ayqy.net/"; app.namespace("Consts").author = "ayqy"; // http://ayqy.net/, ayqy alert(app.Consts.homepage + ", " + app.Consts.author);
模塊化
AMD/CMD,一點擴展知識以下:
CommonJS是一套理論規範(好比js的理論規範是ES),而SeaJS, RequireJS都是對CommonJS的Modules部分的具體實現
CommonJS是面向瀏覽器外(server端)的js制定的,因此是同步模塊加載,SeaJS是CommonJS的一個實現,而RequireJS雖然也是對CommonJS的一個實現,但它是異步模塊加載,算是更貼近瀏覽器的單線程環境
總結:CommonJS的Modules部分提出了模塊化代碼管理的理論,爲了讓js能夠模塊化加載,而RequireJS, SeaJS等各類實現能夠稱爲模塊化腳本加載器
CMD:Common模塊定義,例如SeaJS
AMD:異步模塊定義,例如RequireJS
都是用來定義代碼模塊的一套規範,便於模塊化加載腳本,提升響應速度
CMD與AMD的區別:
CMD依賴就近。便於使用,在模塊內部能夠隨用隨取,不須要提早聲明依賴項,因此性能方面存在些許下降(須要遍歷整個模塊尋找依賴項目)
AMD依賴前置。必須嚴格聲明依賴項,對於邏輯內部的依賴項(軟依賴),以異步加載,回調處理的方式解決
用IIFE(匿名函數當即執行)實現,針對不須要複用的功能模塊能夠用IIFE徹底消除全局變量,因此通常IIFE都是用來輔助命名空間/模塊化方式的
// 事件處理器 function handleClick(event) { var popup = document.getElementById("popup"); popup.style.left = event.clientX + "px"; popup.style.top = event.clientY + "px"; popup.className = "display"; } // 添加事件處理器 ele.addEventListener("click", handleClick);
var app = { // 事件處理 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 = "display"; } }; // 添加事件處理器 // P.S.事件處理器是一個方法聲明,而不是方法調用,沒法傳參,因此須要多一層匿名函數 ele.addEventListener("click", function() { app.handleClick(event); });
var app = { // 事件處理 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 = x + "px"; popup.className = "display"; } }; // 添加事件處理器 ele.addEventListener("click", function() { app.handleClick(event); });
「不要傳遞事件對象」是一條優化原則,在js高程的優化部分也有提到過,但本書給出了詳細理由
直接傳遞事件對象存在如下缺點:
接口定義不明確,參數做用未知
難以測試(重建一個event對象?)
用typeof檢測,但要注意typeof null
返回object
,這不太科學,由於js認爲null是一個空對象的引用
但用 === null檢測DOM元素是合理的,由於null是document.getXXByXXX的可能輸出之一
instanceof並不能準確地檢測子類型,並且不要用它檢測fun和arr,由於不能跨frame
用typeof檢測通常方法;用in檢測DOM方法
用Object.prototype.toString.call(arr) === "[Object Array]"
檢測
注意:ES5有原生的Array.isArray()方法,[IE9+]支持
用in配合hasOwnProperty()檢測
注意:[IE8-]的DOM元素不支持hasOwnProperty(),用的時候要先檢測
硬編碼的值
未來可能會變的值
好比:
URL
須要顯示給用戶的字符串
重複使用的惟一值
設置(例如每頁顯示多少列表項)
可能會變的任何值(很差維護的東西都算配置數據)
先從應用邏輯中分離出配置數據,最簡單的能夠把全部配置數據分級存放在自定義的config對象中
能夠用js文件存儲配置數據,便於加載,但配置數據要嚴格符合js語法,容易出錯。做者建議把配置數據存儲爲格式簡單的屬性文件再用工具轉換爲JSON/JSONP/js格式文件用於加載,做者推薦一個本身寫的工具props2js
用來標誌出乎意料的東西,避免靜默失敗,便於調試
不要拋其它類型,要拋Error對象,由於兼容性,例如:
throw "error: invalid args"; // 有些瀏覽器不顯示該字符串
精肯定位錯誤,建議錯誤信息格式:函數名 + 錯誤緣由
只拋經常使用方法(工具方法)中的錯誤,通常原則:
修復了奇葩bug以後,應該添幾個自定義錯誤,防止錯誤再次發生
若是寫代碼的時候以爲某些點可能引發大麻煩,就應該拋出自定義錯誤
若是代碼是寫給別人用的,應該想一想別人使用的時候可能遇到哪些問題,應該在自定義錯誤中給出提示
finally不經常使用,由於會影響catch中的return
不要留空的catch塊,靜默失敗可能會把問題變得更麻煩
能夠拋出原生的幾種錯誤類型實例,配合instanceof作針對性錯誤處理
P.S.關於具體的幾種錯誤類型以及錯誤處理的更多信息,請查看黯羽輕揚:JS學習筆記8_錯誤處理
原生對象(Object、Array等等)
DOM對象(好比document)
BOM對象(好比window)
庫對象(好比JQuery)
不要重寫方法
不要添新方法,非要改庫功能的話,能夠給庫開發插件
不要刪除方法,不想用的不要刪除,標明過期便可
注意:delete對原型屬性無效,只對實例屬性有用
function Fun() { this.attr1 = 1; // 實例屬性 } Fun.prototype.attr2 = 2; // 原型屬性 // 測試 var obj = new Fun(); alert(obj.attr1 + ", " + obj.attr2); // 1, 2 delete obj.attr1; delete obj.attr2; alert(obj.attr1 + ", " + obj.attr2); // undefined, 2 delete Fun.prototype.attr2; alert(obj.attr1 + ", " + obj.attr2); // undefined, undefined
基於對象的繼承
也就是clone一個新對象,新對象是屬於本身的,能夠隨便改
基於類型的繼承
注意:不要繼承DOM/BOM/Array,由於支持性很差
P.S.關於對象繼承/類型繼承的具體實現,請查看黯羽輕揚:從新理解JS的6種繼承方式
外觀模式
其實就是組合,由於繼承/組合都是實現代碼複用的方式,關於外觀模式的更多信息請查看黯羽輕揚:設計模式以外觀模式(Facade Pattern)
一點題外話:facade與adapter的區別是前者建立了新接口,後者只是實現了已存在的接口,做者一針見血
polyfill 是「在舊版瀏覽器上覆制標準 API 的 JavaScript 補充」。「標準API」指的是 HTML5 技術或功能,例如 Canvas。「JavaScript補充」指的是能夠動態地加載 JavaScript 代碼或庫,在不支持這些標準 API 的瀏覽器中模擬它們。由於 polyfill 模擬標準 API,因此可以以一種面向全部瀏覽器將來的方式針對這些 API 進行開發,最終目標是:一旦對這些 API 的支持變成絕對大多數,則能夠方便地去掉 polyfill,無需作任何額外工做。
關於polyfill的更多信息請查看博客園:[譯]shim和polyfill有什麼區別?
優勢:不須要的時候很容易去掉
缺點:若是polyfill的實現與標準不徹底一致就麻煩了
建議不要用polyfill,應該用原生方法 + 外觀模式來代替,這樣更靈活
應該開嚴格模式,由於非嚴格模式下靜默失敗難以調試
非要UA檢測的話,儘可能向後檢測而不是向前,由於後面的不會再變了
P.S.做者認爲不用管UA會不會變,理由是會設置UA的用戶應該也知道這樣作的後果
特性檢測的通常格式以下:
嘗試標準方式
嘗試特定瀏覽器的實現方式
不支持的話要給出邏輯反饋(好比return null)
例如:
function setAnimation (callback) { // 1.嘗試標準方式 if (window.requestAnimationFrame) { // standard return requestAnimationFrame(callback); } // 2.嘗試特定瀏覽器的實現方式 else if (window.mozRequestAnimationFrame) { // Firefox return mozRequestAnimationFrame(callback); } else if (window.webkitRequestAnimationFrame) { //WebKit return webkitRequestAnimationFrame(callback); } else if (window.oRequestAnimationFrame) { // Opera return window.oRequestAnimationFrame(callback); } else if (window.msRequestAnimationFrame) { // IE return window.msRequestAnimationFrame(callback); } // 3.不支持的邏輯反饋 else { return setTimeout(callback, 0); } }
不能根據一個特性推斷另外一個特性,由於長得像鴨子的東西不必定也像鴨子同樣呱呱叫
不要根據特性去推斷瀏覽器,好比典型的:
if (document.all) { // IE // ... }
這樣作是不對的,不該該嘗試用特徵去描述一個東西,很容易由於條件過少或者過多致使描述不許確
儘可能用直接特性檢測,不行才用UA檢測。至於推斷,就根本不考慮了,沒有任何理由去用推斷