一直以來,關於「代碼規範」的話題都備受關注,業界甚至有不少流傳甚廣的段子不斷調侃之。既然代碼規範能引發這麼大的共鳴,那麼今天咱們談談一個程序員的自我修養——如何寫出優雅的代碼?css
好的接口是流暢易懂的,他主要體現以下幾個方面:html
操做某個元素的css屬性,下面是原生的方法:前端
document.querySelectorAll('#id').style.color = 'red'; 複製代碼
封裝以後node
function a(selector, color) { document.querySelectorAll(selector)[0].style.color = color } a('#a', 'red'); 複製代碼
從幾十個字母長長的一行到簡簡單單的一個函數調用,體現了api簡單易用jquery
a(‘#a’, ‘red’)
是個好函數,幫助咱們簡單實用地改變某個元素,但問題來了,若是第一次使用改函數的人來講會比較困惑,a函數是啥函數,沒有人告訴他。開發接口有必要知道一點,人都是懶惰的,從顏色賦值這個函數來講,雖然少寫了代碼,可是增長了記憶成本。每次作這件事情的時候都須要有映射關係。 a—->color. 若是是簡單的幾個無所謂,可是一般一套框架都有幾十甚至上百的api,映射成本增長會使得程序員哥哥崩潰。 咱們須要的就是使得接口有意義,下面咱們改寫一下a函數:git
function letSomeElementChangeColor(selector, color) { document.querySelectorAll(selector, color); } 複製代碼
letSomeElementChangeColor 相對於a來講被賦予了語言意義,任何人都會知道它的意義程序員
咱們剛剛的函數也是這樣的它太長了letSomeElementChangeColor雖然減小了映射成本,可是增長了記憶成本。要知道,包括學霸在內,任何人都不喜歡被單詞。原生獲取dom的api也一樣有這個問題 document.getElementsByClassName; document.getElementsByName; document.querySelectorAll;這些api給人的感受就是單詞太長了,雖然他給出的意義是很清晰,然而這種作法是創建在犧牲簡易性的基礎上進行的。因而咱們又再次改寫這個以前函數github
function setColor(selector, color) { // ... } 複製代碼
在乎義不作大的變化前提下,縮減函數名稱。使得它易讀易記易用;web
所謂延伸就是指函數的使用像流水同樣按照書寫的順序執行造成執行鏈條:json
document.getElementById('id').style.color = 'red'; document.getElementById('id').style.fontSize = '12px'; document.getElementById('id').style.backgourdColor = 'pink'; 複製代碼
用咱們以前的以前的方法是再次封裝兩個函數 setFontSize, setbackgroundColor; 而後執行它們 setColor(‘id’, ‘red’); setFontSiez(‘id’, ’12px’); setbackgroundColor(‘id’, ‘pink’); 顯然,這樣的作法沒有懶出境界來;id元素每次都須要從新獲取,影響性能,失敗;每次都須要添加新的方法 失敗 每次還要調用這些方法,仍是失敗。下面咱們將其改寫爲能夠延伸的函數 首先將獲取id方法封裝成對象,而後再對象的每一個方法中返回這個對象:
function getElement(selector) { this.style = document.querySelecotrAll(selector).style; } getElement.prototype.color = function(color) { this.style.color = color; return this; } getElement.prototype.background = function(bg) { this.style.backgroundColor = color; return this; } getElement.prototype.fontSize = function(size) { this.style.fontSize = size; return this; } //調用 var el = new getElement('#id') el.color('red').background('pink').fontSize('12px'); 複製代碼
簡單、流暢、易讀後面咱們會在參數裏面講到如何繼續優化。因此,你們都比較喜歡用jquery的api,雖然一個$符號並不表明任何現實意義,但簡單的符號有利於咱們的使用。它體現了以上的多種原則,簡單,易讀,易記,鏈式寫法,多參處理。
糟糕的:
document.getElementById('id').style.color = 'red'; document.getElementById('id').style.fontSize = '12px'; document.getElementById('id').style.backgourdColor = 'pink'; 複製代碼
指望:
$('id').css({color:'red', fontSize:'12px', backgroundColor:'pink'}) 複製代碼
相關的接口保持一致的風格,一整套 API 若是傳遞一種熟悉和溫馨的感受,會大大減輕開發者對新工具的適應性。 命名這點事:既要短,又要自描述,最重要的是保持一致性 「在計算機科學界只有兩件頭疼的事:緩存失效和命名問題」 — Phil Karlton 選擇一個你喜歡的措辭,而後持續使用。選擇一種風格,而後保持這種風格。
糟糕的:
setColor,
letBackGround
changefontSize
makedisplay
複製代碼
指望:
setColor;
setBackground;
setFontSize
set.........
複製代碼
儘可能地保持代碼風格和命名風格,令人讀你的代碼像是閱讀同一我的寫的文章同樣。
下一條原則是平衡,組織元素時不會讓某個部分過於重量級而蓋過其它部分,使用時不穩定。藝術做品裏,平衡就是視覺權重。即便不對稱,做品中仍能感受到不對稱下的平衡,由於它遵循某種模式。上下文中的API設計的平衡,我特指代碼的視覺權重和可預測性(看得出功能)。
平衡的API讓人以爲其組成部分屬於彼此,他們行爲相同,或互補地完成一個目標。經過擴展,APIs也能夠感受平衡,它們容許開發人員簡單的預測其餘API並使用。如Modernizr的屬性測試,它們的平衡性在兩個方面,a)屬性名對應HTML5和CSS術語和API名稱,b)每一個屬性測試統一地返回true或false值。
Modernizr.geolocation
Modernizr.localstorage
Modernizr.webworkers
Modernizr.canvas
Modernizr.borderradius
Modernizr.boxshadow
Modernizr.flexbox
複製代碼
訪問一個單一的屬性來告訴開發者須要瞭解到的相關屬性,以便經過它訪問每個其餘屬性,一個高質量API的強大之處就在於它的簡單。平衡性也保證了我寫和Modernizr交互的代碼在每次讀寫時具備相同的視覺加權。如何在我使用和訪問API時看起來和感受上同樣,而不顧個人慣例。另外一方面,若是Modernizr添加了一個polyfill Canvas的API,不只僅是類庫的視覺加權受到新API的影響,Modernizr的範圍和用途也將大大擴大,而且我在和API交互時可預測性也受到了限制。
達到平衡的另外一種方式是經過依靠開發人員對概念的熟悉得到可預測性的結果。一個典型的例子就是jQuery’s selector syntax(jquery選擇器的語法),它映射css1-3的選擇器到本身的DOM選擇器引擎:
$("#grid") // Selects by ID $("ul.nav > li") // All LIs for the UL with class "nav" $("ul li:nth-child(2)") // Second item in each list 複製代碼
經過使用一個熟悉的概念而且映射到本身的類庫,jquery避免了新的選擇器語法,同事也建立了一個機制讓新用戶經過一個可預測的API快速的把類庫應用到生產.。
判斷參數的類型爲你的程序提供穩定的保障
//咱們規定,color接受字符串類型 function setColor(color) { if(typeof color !== 'string') return; // ..dosomething } 複製代碼
使用json的方式傳值不少好處,它能夠給參數命名,能夠忽略參數的具體位置,能夠給參數默認值等等 好比下面這種糟糕的狀況:
function fn(param1, param2...............paramN) 複製代碼
你必須對應地把每個參數按照順序傳入,不然你的方法就會偏離你預期去執行,正確的方法是下面的作法。
function fn(json) { //爲必須的參數設置默認值 var default = extend({ param: 'default', param1: 'default' // ... }, json) } 複製代碼
這段函數代碼,即使你不傳任何參數進來,他也會預期運行。由於在聲明的時候,你會根據具體的業務決定參數的缺省值。
軟件設計最重要的原則之一:永遠不修改接口,指擴展它!可擴展性同時會要求接口的職責單一,多職責的接口很難擴展。 舉個栗子:
須要同時改變某個元素的字體和背景
糟糕的:
function set(selector, color) { document.querySelectroAll(selector).style.color = color; document.querySelectroAll(selector).style.backgroundColor = color; } 複製代碼
沒法擴展改函數,若是須要再次改變字體的大小的話,只能修改此函數,在函數後面填加改變字體大小的代碼
指望:
function set(selector, color) { var el = document.querySelectroAll(selector); el.style.color = color; el.style.backgroundColor = color; return el; } 複製代碼
須要設置字體、背景顏色和大小
function setAgain (selector, color, px) { var el = set(selector, color) el.style.fontSize = px; return el; } 複製代碼
以上只是簡單的添加顏色,業務複雜而代碼又不是你寫的時候,你就必須去閱讀以前的代碼再修改它,顯然是不符合開放-封閉原則的。修改後的function是返回了元素對象,使得下次須要改變時再次獲得返回值作處理。
可擴展性還包括對this的以及call和apply方法的靈活運用:
function sayBonjour() { alert(this.a) } obj.a = 1; obj.say = sayBonjour; obj.say();//1 //or sayBonjour.call||apply(obj); //1 複製代碼
能夠用 類型檢測 typeof 或者try…catch。 typeof 會強制檢測對象不拋出錯誤,對於未定義的變量尤爲有用。
大多數開發者不但願出錯了還須要本身去找帶對應得代碼,最好方式是直接在console中輸出,告訴用戶發生了什麼事情。咱們能夠用到瀏覽器的輸出api:console.log/warn/error。你還能夠爲本身的程序留些後路: try…catch。
function error (a) { if(typeof a !== 'string') { console.error('param a must be type of string') } } function error() { try { // some code excucete here maybe throw wrong }catch(ex) { console.wran(ex); } } 複製代碼
可預見性味程序接口提供健壯性,爲保證你的代碼順利執行,必須爲它考慮到非正常預期的狀況。咱們看下不能夠預見的代碼和可預見的代碼的區別用以前的setColor
糟糕的:
function set(selector, color) { document.getElementById(selector).style.color = color; } 複製代碼
指望:
zepto.init = function(selector, context) { var dom // If nothing given, return an empty Zepto collection if (!selector) return zepto.Z() // Optimize for string selectors else if (typeof selector == 'string') { selector = selector.trim() // If it's a html fragment, create nodes from it // Note: In both Chrome 21 and Firefox 15, DOM error 12 // is thrown if the fragment doesn't begin with < if (selector[0] == '<' && fragmentRE.test(selector)) dom = zepto.fragment(selector, RegExp.$1, context), selector = null // If there's a context, create a collection on that context first, and select // nodes from there else if (context !== undefined) return $(context).find(selector) // If it's a CSS selector, use it to select nodes. else dom = zepto.qsa(document, selector) } // If a function is given, call it when the DOM is ready else if (isFunction(selector)) return $(document).ready(selector) // If a Zepto collection is given, just return it else if (zepto.isZ(selector)) return selector else { // normalize array if an array of nodes is given if (isArray(selector)) dom = compact(selector) // Wrap DOM nodes. else if (isObject(selector)) dom = [selector], selector = null // If it's a html fragment, create nodes from it else if (fragmentRE.test(selector)) dom = zepto.fragment(selector.trim(), RegExp.$1, context), selector = null // If there's a context, create a collection on that context first, and select // nodes from there else if (context !== undefined) return $(context).find(selector) // And last but no least, if it's a CSS selector, use it to select nodes. else dom = zepto.qsa(document, selector) } // create a new Zepto collection from the nodes found return zepto.Z(dom, selector) } 複製代碼
以上是zepto的源碼,能夠看見,做者在預見傳入的參數時作了不少的處理。其實可預見性是爲程序提供了若干的入口,無非是一些邏輯判斷而已。zepto在這裏使用了不少的是非判斷,同時致使了代碼的冗長,不適合閱讀。總之,可預見性真正須要你作的事多寫一些對位置實物的參數。把外部的檢測改成內部檢測。是的使用的人用起來舒心放心開心。吶!作人嘛最重要的就是海森啦。
一個最好的接口是不須要文檔咱們也會使用它,可是每每接口量一多和業務增長,接口使用起來也會有些費勁。因此接口文檔和註釋是須要認真書寫的。註釋遵循簡單扼要地原則,給多年後的本身也給後來者看:
//註釋接口,爲了演示PPT用 function commentary() { //若是你定義一個沒有字面意義的變量時,最好爲它寫上註釋:a:沒用的變量,能夠刪除 var a; //在關鍵和有歧義的地方寫上註釋,猶如畫龍點睛:路由到hash界面後將全部的數據清空結束函數 return go.Navigate('hash', function(){ data.clear(); }); } 複製代碼
文章參考:賣燒烤夫斯基