持續更新地址 https://wdd.js.org/js-best-pr...
一千個讀者有一千個哈姆雷特
,每一個人都有本身的code style。我也曾爲了要不要加分號給同事鬧個臉紅脖子粗,實際上有必要嗎? 其實JavaScript已經有了比較流行的幾個風格javascript
我本身使用的是JavaScript Standard Style
, 我之因此使用這個,是由於它有一些工具。可讓你寫完代碼後,一旦保存,就自動幫你把你的風格的代碼修正成標準分割,而不是死記硬背應該怎麼寫。看完這個頁面,你就應該立馬愛上JavaScript Standard Style , 若是你用vscode, 剛好你有寫vue, 你想在.vue文件中使用standard風格,那麼你須要看看這篇文章php
不少時候,咱們不是從零開始,開發新代碼。而是去維護別人的代碼,以他人的工做成果爲基礎。確保本身的代碼可維護,是贈人玫瑰,手留餘香的好事。一方面讓別人看的舒服,另外一方面也防止本身長時間沒看過本身的代碼,本身都難以理解。
可維護的代碼的一些特徵css
可理解
易於理解代碼的用途可適應
數據的變化,不須要徹底重寫代碼可擴展
要考慮將來對核心功能的擴展可調試
給出足夠的信息,讓調試的時候,肯定問題所在不可分割
函數的功能要單一,功能粒度不可分割,可複用性加強// 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]; } } } }
There are only two hard problem in Computer Science cache invalidation and naming things.---Phil Karlton
// Good var count = 10; var myName = "wdd"; var found = true; // Bad: Easily confused with functions var getCount = 10; var isFound = true; // Good function getName() { return myName; } // Bad: Easily confused with variable function theName() { return myName; } // Bad: var btnOfSubmit = $('#submit'); // Good: var $btnOfSubmit = $('#submit'); // Bad:給App添加一個處理聊天事件的函數,通常都是和websocket服務端推送消息相關 App.addMethod('createChat',function(res){ App.log(res); }); // Bad: 此處調用,這裏很容易誤覺得這個函數是處理建立聊天的邏輯函數 App.createChat(); // Good: App.addMethod('onCreateChat',function(res){ App.log(res); }); // Good:此處調用 App.onCreateChat();
變量命名不只僅是一種科學,更是一種藝術。總之,要短小精悍,見名知意。有些名詞能夠反應出變量的類型。
變量名
名詞 | 數據類型含義 |
---|---|
count, length,size | 數值 |
name, title,message | 字符串 |
i, j, k | 用來循環 |
car,person,student,user | 對象 |
success,fail | 布爾值 |
payload | post數據的請求體 |
method | 請求方式 |
函數名
動詞 | 含義 |
---|---|
can | Function returns a boolean |
has | Function returns a boolean |
is | Function returns a boolean |
get | Function returns a nonboolean |
set | Function is used to save a value |
一些與函數名搭配的經常使用動詞
動詞 | 用法 |
---|---|
send | 發送 |
resend | 重發 |
validate | 驗證 |
query | 查詢 |
create | 建立 |
add | 添加 |
delete | 刪除 |
remove | 移除 |
insert | 插入 |
update | 更新,編輯 |
copy | 複製 |
render | 渲染 |
close | 關閉 |
open | 開啓 |
clear | 清除 |
edit | 編輯 |
query | 查詢 |
on | 當事件發生 |
list | 渲染一個列表,如用戶列表renderUsersList() |
content | 渲染內容,如用戶詳情的頁面 renderUserContent() |
接口經常使用的動詞
對於http請求的最經常使用的四種方法,get,post,put,delete,有一些經常使用的名詞與其對應html
含義 | 請求方法 | 詞語 | 栗子 |
---|---|---|---|
增長 | post | create | createUser,createCall |
刪除 | delete | delete | deleteUser |
修改 | put | update | updateUser,updateProfile |
查詢 | get | get,query | getUser,queryUser(無條件查詢使用get,有條件查詢使用query) |
學會使用單複數命名函數
函數名 | 含義 |
---|---|
getUser() | 獲取一個用戶,通常是經過惟一的id來獲取 |
getUsers() | 獲取一組用戶,通常是經過一些條件來獲取 |
createUser() | 建立一個用戶 |
createUsers() | 建立一組用戶 |
常量
var MAX_COUNT = 10; var URL = "http://www.nczonline.net/";
構造函數
// Good function Person(name) { this.name = name; } Person.prototype.sayName = function() { alert(this.name); }; var me = new Person("wdd");
底層http請求接口函數
eg:前端
app-main.js app-event.js app-user-manger.js
本身寫的js文件最好和引用的一些第三方js分別放置在不一樣的文件夾下。vue
alert的缺點
java
更優雅的提醒方式
webpack
//通常事件訂閱的寫法,以jQuery的寫法爲栗子 $(document).on('click','#btn-get-users',function(event){ event.stopPropagation(); //下面的省略號表示執行獲取全部用於並顯示在頁面上的邏輯 // Bad ... ... ... // });
若是增長了需求,當點擊另一個按鈕的時候,也要執行獲取全部用戶並顯示在頁面上,那麼上面省略的代碼又要複製一份。若是接口有改動,那麼須要在兩個不一樣的地方都要修改。
因此,應該這樣。git
$(document).on('click','#btn-get-users',function(event){ event.stopPropagation(); //將應用邏輯分離在其餘個函數中 // Good App.getUsers(); App.renderUsers(); });
// Bad ReqApi.tenant.queryUsers({},function(res){ if(!res.success){ console.error(res); return; } //對數據的處理 ... ... ... });
上面代碼對數據的處理直接寫死在異步請求裏面,若是換了一個請求,可是數據處理方式是同樣的,那麼又要複製一遍數據處理的代碼。最好的方式是將數據處理模塊化成爲一個函數。github
// Good ReqApi.tenant.queryUsers({},function(res){ if(!res.success){ console.error(res); return; } //對數據的處理 App.renderUsers(res.data); });
異步請求只處理請求,不處理數據。函數的功能要專注,功能粒度不可分割。
若是你須要一個函數去驗證輸入框是不是空,以下。這種方式就會綁定死了這個只能驗證id爲test的輸入框,換成其餘的就不行
// bad function checkInputIsEmpty(){ var value = $('#test').val(); if(value){ return true; } else{ return false; } } // good function isEmptyInput(id){ var value = $('#'+id).val(); if(value){ return true; } else{ return false; } }
javascript動態性質是的幾乎任何東西在任什麼時候間都能更改,這樣就很容易覆寫了一些默認的方法。致使一些災難性的後果。若是你不負責或者維護某個對象,那麼你就不能對它進行修改。
// Bad 兩個全局變量 var name = "wdd"; funtion getName(){ console.log(name); } // Good 一個全局變量 var App = { name:"wdd", sayName:funtion(){ console.log(this.name);//若是這個函數當作回調數使用,這個this可能指向window, } };
單一的全局變量即是命名空間的概念,例如雅虎的YUI,jQuery的$等。
funtion sortArray(values){ // 避免 if(values != null){ values.sort(comparator); } }
function sortArray(values){ // 推薦 if(values instanceof Array){ values.sort(compartor); } }
代碼中與null比較越少,就越容易肯定代碼的目的,消除沒必要要的錯誤。
配置數據是一些硬代碼(hardcoded),看下面的栗子
function validate(value){ if(!value){ alert('Invalid value'); location.href = '/errors/invalid.php'; } }
上面代碼裏有兩個配置數據,一個是UI字符串('Invalid value'),另外一個是一個Url('/error/invalid.php')。若是你把他們寫死在代碼裏,那麼若是當你須要修改這些地方的時候,那麼你必須一處一處的檢查並修改,並且還可能會遺漏。
var Config = { "MSG_INVALID_VALUE":"Invalid value", "URL_INVALID":"/errors/invalid.php" }
在開發過程當中,可能隨處留下幾個console.log,或者alert語句,這些語句在開發過程當中是頗有價值的。可是項目一旦進入生產環境,過多的console.log可能影響到瀏覽器的運行效率,過多的alert會下降程序的用戶體驗。而咱們最好不要在進入生產環境前,一處一處像掃雷同樣刪除或者註釋掉這些調試語句。
最好的方式是設置一個開關。
//全局命令空間 var App = { debug:true, log:function(msg){ if(debug){ console.log(msg); } }, alert:function(msg){ if(debug){ alert(msg); } } }; //使用 App.log('獲取用戶信息成功'); App.alert('密碼不匹配'); //關閉日誌輸出與alert App.debug = false;
沒使用promise以前的回調函數寫法
// bad:沒使用promise以前的回調函數寫法 function sendRequest(req,successCallback,errorCallback){ var inputData = req.data || {}; inputData = JSON.stringify(inputData); $.ajax({ url:req.base+req.destination, type:req.type || "get", headers:{ sessionId:session.id }, data:inputData, dataType:"json", contentType : 'application/json; charset=UTF-8', success:function(data){ successCallback(data); }, error:function(data){ console.error(data); errorCallback(data); } }); } //調用 sendRequest(req,function(res){ ... },function(res){ ... });
使用promise以後
function sendRequest(req){ var dfd = $.Deferred(); var inputData = req.data || {}; inputData = JSON.stringify(inputData); $.ajax({ url:req.base+req.destination, type:req.type || "get", headers:{ sessionId:session.id }, data:inputData, dataType:"json", contentType : 'application/json; charset=UTF-8', success:function(data){ dfd.resolve(data); }, error:function(data){ dfd.reject(data); } }); return dfd.promise(); } //調用 sendRequest(req) .done(function(){ //請求成功 ... }) .fail(function(){ //請求失敗 ... });
假如前端要去接口獲取用戶信息並顯示出來,若是你的請求格式是正確的,可是接口返回400以上的錯誤,你必須經過提醒來告知測試,這個錯誤是接口的返回錯誤,而不是前端的邏輯錯誤。
對資源的操做包括獲取、建立、修改和刪除資源,這些操做正好對應HTTP協議提供的GET、POST、PUT和DELETE方法。
對應方式
請求類型 | 接口前綴 |
---|---|
GET | .get, |
POST | .create 或者 .get |
PUT | .update |
DELETE | .delete |
說明
示例:
// 與用戶相關的接口 App.api.user = {}; // 獲取一個用戶: 通常來講是一個指定的Id,例如userId App.api.user.getUser = function(){ ... }; // 獲取一組用戶: 通常來講是一些條件,獲取條件下的用戶,篩選符合條件的用戶 App.api.user.getUsers = function(){ ... }; // 建立一個用戶 App.api.user.createUser = function(){ }; // 建立一組用戶 App.api.user.createUsers = function(){ }; // 更新一個用戶 App.api.user.updateUser = function(){ }; // 更新一組用戶 App.api.user.updateUsers = function(){ }; // 更新一個用戶 App.api.user.updateUser = function(){ }; // 更新一組用戶 App.api.user.updateUsers = function(){ }; // 刪除一個用戶 App.api.user.deleteUser = function(){ }; // 刪除一組用戶 App.api.user.deleteUsers = function(){ };
優化循環
減值迭代
:從最大值開始,在循環中不斷減值的迭代器更加高效簡化終止條件
:因爲每次循環過程都會計算終止條件,因此必須保證它儘量快。也就是避免其餘屬性查找簡化循環體
:因爲循環體是執行最多的,因此要確保其最大限度地優化。// **Bad** 某些代碼求值 eval("alert('hello')"); // **Bad** 建立新函數 var sayHi = new Function("alert('hello')"); // **Bad** 設置超時 setTimeout("alert('hello')");
性能的其餘注意事項
case 的分支不要超過128條
廢棄
)// 方式1:Bad var count = 5; var name = 'wdd'; var sex = 'male'; var age = 10; // 方式2:Good var count = 5, name = 'wdd', sex = 'male', age = 10;
2017-03-07 理論上方式2可能要比方式1性能高一點。可是我在實際使用中,這個快一點幾乎是沒什麼感覺的。就像你沒法感覺到小草的生長同樣。反而可讀性更爲重要。因此,每行最好只定義一個變量,而且每行都有一個var,並用分號結尾。
// Good var name = values[i++];
// Good var values = ['a','b','c']; var person = { name:'wdd', age:10 };
只要有可能,儘可能使用數組和對象字面量的表達式來消除沒必要要的語句
在JavaScript各個方面中,DOM無疑是最慢的一部分。DOM操做與交互要消耗大量的時間。由於他們每每須要從新渲染整個頁面或者某一部分。進一步說,看似細微的操做也可能花好久來執行。由於DOM要處理很是多的信息。理解如何優化與DOM的交互能夠極大的提升腳本完成的速度。
調用頻率很是高的dom查找,能夠將DOM緩存在於一個變量中
// 最簡單的dom緩存 var domCache = {}; function myGetElement(tag){ return domCache[tag] = domCache[tag] || $(tag); }
// 先看下面的極端狀況 app.user.mother.parent.home.name = 'wdd' app.user.mother.parent.home.adderess = '上海' app.user.mother.parent.home.weather = '晴天' // 更優雅的方式 var home = app.user.mother.parent.home; home.name = 'wdd'; home.address = '上海', home.weather = '晴天'
注意
使用上面的方式是有前提的,必須保證app.user.mather.parent.home是一個對象,由於對象是傳遞的引用。若是他的類型是一個基本類型,例如:number,string,boolean,那麼複製操做僅僅是值傳遞,新定義的home的改變,並不會影響到app.user.mather.parent.home的改變。
+'4.1' === 4.1
4.1+'' === '4.1'
'4.99' | 0 === 4
建議讀者自行擴展
DRY(dont't repeat yoursele: 不要重複你本身)
高內聚低耦合
開放閉合
最小意外
單一職責(single responsibility)
建議使用Object.prototype.toString.call()方法檢測數據類型
function isArray(value){ return Object.prototype.toString.call(value) === "[object Array]"; } function isFunction(value){ return Object.prototype.toString.call(value) === "[object Function]"; } function isRegExp(value){ return Object.prototype.toString.call(value) === "[object RegExp]"; } function isNativeJSON(){ return window.JSON && Object.prototype.toString.call(JSON) === "[object JSON]"; }
對於ie中一COM對象形式實現的任何函數,isFunction都返回false,由於他們並不是原生的javascript函數。
在web開發中,可以區分原生與非原生的對象很是重要。只有這樣才能確切知道某個對象是否有哪些功能
以上全部的正確性的前提是:Object.prototype.toString沒有被修改過
function Person(name){ this.name = name; } //使用new來建立一個對象 var one = new Person('wdd'); //直接調用構造函數 Person();
因爲this是運行時分配的,若是你使用new來操做,this指向的就是one。若是直接調用構造函數,那麼this會指向全局對象window,而後你的代碼就會覆蓋window的原生name。若是有其餘地方使用過window.name, 那麼你的函數將會埋下一個深藏的bug。
那麼,如何才能建立一個做用域安全的構造函數?
function Person(name){ if(this instanceof Person){ this.name = name; } else{ return new Person(name); } }
假設有一個方法X,在A類瀏覽器裏叫A,在b類瀏覽器裏叫B,有些瀏覽器並無這個方法,你想實現一個跨瀏覽器的方法。
惰性載入函數的思想是:在函數內部改變函數自身的執行邏輯
function X(){ if(A){ return new A(); } else{ if(B){ return new B(); } else{ throw new Error('no A or B'); } } }
換一種寫法
function X(){ if(A){ X = function(){ return new A(); }; } else{ if(B){ X = function(){ return new B(); }; } else{ throw new Error('no A or B'); } } return new X(); }
// 下面代碼在谷歌瀏覽器中執行 > var person = {name: 'wdd'}; undefined > Object.preventExtensions(person); Object {name: "wdd"} > person.age = 10 10 > person Object {name: "wdd"} > Object.isExtensible(person) false
密封對象不可擴展,而且不能刪除對象的屬性或者方法。可是屬性值能夠修改。
> var one = {name: 'hihi'} undefined > Object.seal(one) Object {name: "hihi"} > one.age = 12 12 > one Object {name: "hihi"} > delete one.name false > one Object {name: "hihi"}
最嚴格的防篡改就是凍結對象。對象不可擴展,並且密封,不能修改。只能訪問。
函數節流的思想是:某些代碼不能夠沒有間斷的連續重複執行
var processor = { timeoutId: null, // 實際進行處理的方法 performProcessing: function(){ ... }, // 初始化調用方法 process: function(){ clearTimeout(this.timeoutId); var that = this; this.timeoutId = setTimeout(function(){ that.performProcessing(); }, 100); } } // 嘗試開始執行 processor.process();
頁面若是有十個區域要動態顯示當前時間,通常來講,能夠用10個定時來實現。其實一箇中央定時器就能夠搞定。
中央定時器動畫 demo地址:http://wangduanduan.coding.me...
var timers = { timerId: 0, timers: [], add: function(fn){ this.timers.push(fn); }, start: function(){ if(this.timerId){ return; } (function runNext(){ if(timers.timers.length > 0){ for(var i=0; i < timers.timers.length ; i++){ if(timers.timers[i]() === false){ timers.timers.splice(i, 1); i--; } } timers.timerId = setTimeout(runNext, 16); } })(); }, stop: function(){ clearTimeout(timers.timerId); this.timerId = 0; } };
推薦閱讀:JS函數式編程中文版
ajax在使用的時候,例如點擊按鈕,獲取某個列表。須要注意如下方面
儘可能將全部代碼封裝在函數中,不要暴露全局變量
每一個函數的函數體中,代碼行越少越好,最好一個函數中就一句代碼
若是你認爲前端不須要關於協議的知識,那麼你就是大錯特錯了。其實不只僅是前端,全部的開發者都應該學習底層的協議。由於他們是互聯網通訊的基石。
推薦三本必讀的書籍
或者你一也能夠看看關於協議方面的一些問題,以及若是你遇到過,你是否知道如何解決: