javascript編碼標准


前面的話

  編碼標准是有爭議的。幾乎每個人都有自己的標准,但對標准應該是什么樣的,則似乎很少能達成共識。但編碼標准意味着,通過共同語言和一致的結構,把開發人員從無意義的工作中解放出來。允許開發人員把創新精神放在重要的邏輯上面。一個好的標准能提供清晰明了的意圖,是有效工作所必需的。本文將詳細介紹Javascript編碼標准

 

引入

  給像JavaScript這種松散類型(loosely typed)的動態語言定義明確的標准,幾乎可以肯定,要比給較為嚴格的語言定義標准來得更加重要。JavaScript的高度靈活性,可能會使它成為編碼語法和實踐的潘多拉魔盒。較為嚴格的語言,本身就具備結構性和一致性,而JavaScript需要准則和應用標准才能達到相同的效果

  維護代碼要比編寫代碼花費更多的時間。要編寫越是容易理解的代碼,在最開始就越是需要深思熟慮和良好的結構

  好的Javascript編碼應該符合以下標准

  1、編碼錯誤的可能性降至最低

  2、代碼適合大規模的項目和團隊(一致的、可讀的、可擴展的和可維護的)

  3、鼓勵編碼的效率、效果和重用

  4、鼓勵使用JavaScript的優點,避免使用它的缺點

  5、開發團隊的每個成員都使用

 

代碼布局

  通常,我們的代碼被閱讀的次數比編寫它的次數要多得多。對代碼規定格式和應用約定,以便我們的開發同事(包括幾個星期之后的我們自己),能夠很容易地理解代碼的內容

【使用一致的縮進和行長】

  報紙上的文本列都在50~80個字符的長度之間。對人類的眼睛來說,超過80個字符的行,看起來會逐漸變得吃力。一般地,閱讀理解的最佳行長(linelength)在45~75個字符之間,66個字符的行長被認為是最舒適的。最好使用短制表符(2個空格)和稍短的行長(78個字符),每一行更窄一些,重要的內容也更易讀一些。使用短制表符也是意識到,像JavaScript這種事件驅動的語言比純過程語言,縮進要小一些,因為JavaScript有大量的回調函數和閉包

  1、每級代碼縮進兩個空格

  2、每行限制為78個字符

【按段落組織代碼】

  在編排代碼的時候,要以清晰明白為目標,而不是減少代碼的字節數。一旦代碼發布到生產環境,在傳輸給用戶之前,JavaScript代碼會合並(concatenated)、壓縮(minified)。結果,那些用來幫助理解的工具(空白、注釋和更具描述性的變量名)對性能毫無影響。通過合理的使用空白符(white space),使代碼更易讀

  1、按邏輯段落組織代碼,段落之間要空行

  2、每一行最多只包含一條語句或賦值語句,但是允許每行同時聲明多個變量

  3、運算符和變量之間要有空格,這樣就能更容易地識別變量

  4、每個逗號之后要有空格

  5、在段落內,相似的運算符要對齊

  6、縮進注釋,縮進量和所解釋的代碼相同

// 把一個或多個聲明放在一行上,但每行只有一條賦值語句
var 
  x, y, r, print_msg, get_rangdom,
  coef      = 0.5,
  rot_delta = 1,
  x_delta   = 1,
  y_delta   = 1,
  first_name  = 'sally'
  ;

// 在下一行段落的前面添加空行
// function to write text to message container
print_msg = function ( msg_text ){
  //縮進注釋,和它所描述的段落層級一致
  // .text() prevents xss injection
  $('#sl').text( msg_text );
};

// function to return a random number
get_rangdom = function ( num_arg ){
  return Math.random() * numn_arg;
};

// initialize coorainates
x = get_random(  10 );
y = get_random(  20 );
r = get_random( 360 );

// 添加空白,對齊相似的元素,相似的語句更容易閱讀
// adjust to offsets
x += x_delta    * coef;
y += y_delta    * coef;
r += rot_delta  * coef;

【換行要一致】

  1、在運算符的前面換行,因為人們檢查左列的所有運算符是很容易的

  2、把后續的語句縮進一個層次,比如使用兩個空格

  3、在逗號分隔符的后面換行

  4、方括號或者括號單獨占一行。清楚地表明這是語句的結尾,不會迫使讀者橫向掃尋分號

// 將運算符放在左邊,排成一列
long_quote = 'Four score and seven years ago our '
  + 'fathers brought forth on this continent, a new ,
  + 'nation, conceived in Liberty, '
  + 'and dedicated to the proposition that '
  + 'all men are created equal. ';

// 方括號單獨占一行,下一條語句就容易識別了
// 使用尾部逗號,更容易維護
cat_breed_list = [
  'Abyssinian',         'American Bobtail',     'American Curl',
  'American Shorthair', 'American Whiterhair',  'Balinses',
  'Balinese-Javanaese', 'Birman',               'Bombay'
];

【使用K&R風格的括號】

  K&R風格的括號可以平衡垂直空間的使用,增加可讀性。當格式化對象和映射、數組、復合語句或者調用的時候,應該使用K&R風格的括號

  1、如果可能,就使用單行。比如,當一個很短的數組聲明能寫在一行上的時候,就沒必要把它拆分成三行。

  2、把左括號、左花括號或者左方括號放在開始行的末尾。

  3、在分隔符(括號、花括號或者方括號)的里面把代碼縮進一個層級,比如,兩個空格。

  4、右括號、右花括號或者右方括號單獨占一行,縮進和開始行相同

var
  run_count,        full_name,      top_fruit_list,
  full_fruit_list,  print_string;

run_count = 2;
full_name = 'Fred Burns';
top_fruit_list = ['Apple', 'Banana', 'Orange' ];

// 使用垂直對齊,增加可讀性
full_fruit_list = [
  'Apple',      'Apricot',  'Banana',     'Blackberry', 'Blueberry',
  'Current',    'Cherry',   'Date',       'Grape',      'Grapefruit',
  'Guava',      'Kiwi',     'Kumquat',    'Lemon',      'Lime',
  'Lychee',     'Mango',    'Melon',      'Nectarine',  'Orange',
  'Peach',      'Pear',     'Pineapple',  'Raspberry',  'Strawberry',
  'Tangerine',  'Ugli'
]

// 使用K&R風格的括號
print_string = function ( text_arg ){
  var text_arg, char_list, i;
  char_list = input_text.split('');
  for( i = 0, i < char_list.length; i++ ){
    document.write( char_list[i] );
  }
  return true;
}

print_string( 'We have counted' + 
  + String( run_count )
  + ' invocations to date;
);

【使用空格來區別函數和關鍵字】

  很多語言有冠詞的概念,像an、a或者the這種單詞。冠詞的目的之一是提醒讀者或者聽者,下一個單詞將是名詞或者名詞短語。和函數以及關鍵字一起使用的空格,可以達到類似的效果

  1、函數名后面沒有空格。在函數名和左括號“(”之間沒有空格

  2、關鍵字后面空一格,然后是左括號“(”

  3、當格式化for語句的時候,在每個分號的后面空一格

mystery_text = get_mystery( 'Hello JavaScript Denizens' );
for ( x = 1; x < 10; x++ )    { console.log( x ); }

【引號要一致】

  使用單引號作為字符串的定義符號,而不是雙引號,因為HTML中標准屬性的定義符是雙引號

html_snip = '<input name="alley_cat" type="text" value="bone">';

 

注釋說明

  注釋可能要比它們所解釋的代碼更加重要,因為它們能傳達在其他方面不明顯的關鍵細節。這在事件驅動編程中尤其明顯,因為大量的回調函數,導致跟蹤代碼的執行要耗費掉大量時間。這並不意味着添加更多的注釋總是更好的。擺放有策略、信息量大和精心維護的注釋,價值是很高的,而雜亂無章文不對題的注釋,還不如沒有的好

【解釋代碼】

  好的注釋的標准是將注釋的數量最小化,將注釋的價值最大化。通過約定來減少注釋,盡可能地讓代碼進行自我說明。通過將注釋和它們所描述的段落對齊,並確保它們的內容對讀者是有價值的,從而使注釋的價值最大化

  使用一致的、有意義的變量名,能提供更多的信息,需要的注釋很少

var
  welcome_html = '<h1>Welcom tho Color house</h1>',
  house_color_list = ['yellow', 'green', 'little pink'],
  spec_map, get_spec_map,  run_init;

// Begin /get_spec_map/
// Get a specification map based on colors
get_spec_map = function ( color_list_arg ){
  var 
    color_count = color_list_arg.length,
    spec_map    = {},
    i;
  for ( i = 0, i < color_count; i++ ){
    // ... 30 more lines
  } 
  return spec_map;
}
// End /get_spec_map/

run_init = function () {
  var spec_map = get_spec_map( house_color_list );
  $('#welocome').html('welcome_html');
  $('#specs').text( JSON.stringify( spec_map ) );
};

run_init();

【給API和TODO添加文檔】

  注釋也能為代碼提供更為正式的文檔。總體架構的文檔應該放在專門的架構文檔里面。但是函數或者對象API的文檔,可以並且通常應該放在代碼的旁邊

  1、解釋所有重要的函數,說明它的目的,使用的參教或者設置(setting),它的返回值,以及所有拋出的異常

  2、如果禁用了代碼,要解釋為什么,使用這種格式的注釋://TODO date username-comment。在判斷注釋新鮮度的時候,用戶名和日期是很有價值的,也可以使用自動化工具,在代碼庫中的TODO項上,自動填上用戶名和日期

// BEGIN DOM Method /toggleSlider/
// Purpose : Extends and retracts chat slider 
// Required Arguments :
//    * do_extend (boolean) true extends slider, false retracts
// Optional Arguments :
//    * callback (function) executed after animation is complete
// Settings :
//    *    chat_extend_time, chat_retract_time
//    *    chat_extend_height, chat_retract_height
// Returns : boolean
//    *    true - slider animation activated
//    *    false - slider animation not activated
// Throws : none
//
toggleSlider = function( do_extend, callback )    {
// ...
};
// END DOM Method /toggleSlider/
// BEGIN TODO 2018-01-11 xiaohuochai - debug code disabled
// alert( warning_text );
// ... (lots more lines) ...
//
// END TODO 2018-01-11 xiaohuochai - debug code disabled

【使用命名約定,減少並改進注釋】

  下面是一個示例

var make_house = curry_build_item({ item_type : 'house' });

  通過上面代碼可以得到以下信息

  1、make_house是一個對象構造器。

  2、調用的函數叫做柯里化函數,它使用閉包來維護狀態並返回一個函數

  3、調用的函數接收字符串參數,表示類型(type)

  4、變量的作用域是局部的

  如果使用如下聲明,則需要添加許多注釋

var creator = maker('house');
// 'creator' is an object constructor we get by 
// calling 'maker'. The first positional argument 
// of 'maker' must be a string, and it directs 
// the type of object constructor to be returned.
// 'maker' uses a closure to remember the type
// of object the returned function is to
// meant to create.
var creator = maker('house');

  加了注釋的示例,不但比簡易的示例顯得更為冗長,而且需要更多的時間編寫,很可能是因為我們設法傳遞和命名約定一樣多的信息量。情況會越來越糟糕:經過一段時間以后,注釋容易變得不准確,因為代碼改變了,開發人員變得懶惰了。假如幾個星期之后,我們決定更改了變量名,卻忘記更新注釋中引用這些變量名的地方。現在的注釋完全錯了並且容易誤導別人。不但是這樣,而且所有這些注釋使得代碼難以理解,因為代碼清單長了9倍。沒有注釋是最好的。相比之下,我們更想使用簡單示例中的變量名

 

變量

  每個人在編碼的時候,都會使用命名約定,不管他們是否意識到這一點,就像不做決定也是一種決定。一個好的命名約定,當團隊的所有成員都理解並使用它的時候,能發揮巨大的價值。當他們這么做的時候,就能從枯燥的代碼跟蹤和費力的注釋維護當中解放出來,把精力都集中在代碼的目標和邏輯上面

【使用常用字符】

  1、變量名使用a~z、A~Z、0~9、下划線和$符號

  2、變量名不要以數字開頭

【傳送變量作用域】

  1、當變量作用域是整個模塊時使用駝峰式(模塊名字空間的所有地方都可以訪問該變量)

  2、當變量作用域不是整個模塊時使用下划線 (模塊名字空間內的某個函數的局部變量)

  3、確保所有模塊作用域內的變量至少有兩個音節,這樣作用域就清晰了。比如,不要使用叫做config的變量,可以使用更具描述性的和明顯是模塊作用域的configMap

【命名布爾變量】

  當布爾值表示狀態的時候,我們使用單詞is,比如,is_retracted或者is_stale。當使用布爾值來表示行為的時候(如函數中的參數),我們使用單詞do,像do_retract或者do_extend。當使用布爾值來表示所有權的時候,我們使用has,比如,has_whiskers 或者 has_wheels

指示器            局部作用域        模塊作用域
bool[通用]      bool_return      boolReturn
do(請求行為)     do_retract       doRetract
has(表示包含)    has_whiskers     hasWhiskers
is(表示狀態)    is_retracted      isRetracted

【命名字符串變量】

指示器          局部作用域         模塊作用域
str[通用]      direction_str     directionStr
id            email_id          emailld
date          email_date        emailDate
html          body_html         bodyHtml
msg           employee_msg      employeeMsg
name          emp1oyee_name     employeeName
text          email_text        emailText
type          item_type         itemType

【命名整型變量】

指示器          局部作用域         模塊作用域
int[通用]       size_int          sizeInt
無(約定)        i , j , k         (不允許出現在模塊作用域內)
count          employee_count    employeeCount
index          employee_index    employeeIndex
time(毫秒)      retract_time      retractTime

【命名數字變量】

指示器          局部作用域       模塊作用域
num[通用]       size_num        sizeNum
無(約定)        x, y, z         (不允許出現在模塊作用域內)
coord(坐標)     x_coord        xCoord
ratio          sales_ratio     salesRatio

【命名正則變量】

指示器      局部作用域          模塊作用域
regex      regex_filter      regexFilter

【命名數組變量】

指示器      局部作用域          模塊作用域
list      timestamp_list     timestampList
list      color_list         colorList

【命名映射變量】

指示器        局部作用域                  模塊作用域
map          employee_map              employeeMap
map          receipt_timestamp_map     receiptTimestampMap

【命名對象變量】

  1、對象變量應該是名詞,加上可選的修飾符:emplyee或者receipt

  2、確保模塊作用域的對象變量名具有兩個或者兩個以上的音節,這樣作用域就清晰了: storeEmployee 或者 salesReceipt

  3、jQuery對象有前綴$。目前這種約定很常見,在單頁應用中,jQuery對象(有時候叫集合)很普遍

指示器           局部作用域       模塊作用域
無(單名詞)      employee         storeEmployee
無(單名詞)      receipt          salesReceipt
$             $area_tabs        $areaTabs

【命名函數變量】

  1、命名函數應始終遵循動詞加名詞的形式,比如,get_record或者empty_cache_map

  2、模塊作用域的函數應始終包含兩個或兩個以上的音節,這樣作用域就清晰了:getRecord 或者 emptyCacheMap

  3、動詞含義要一致

指示器        局部作用域           模塊作用域         指示器含義     
fn[通用]     fn_sync            fnSync            通用函數指示器
curry       curry_make_user    curryMakeUser     返回指定參數的函數
destroy     destroy_entry      destroyEntry      移除數據結構,意味着必要時會回收數據引用
remove      remove_element     removeElement     移除數據結構的另一種寫法
empty       empty_cache_map    emptyCacheMap     移除數據結構的一些或者全部成員,不會移除容器
fetch       fetch_user_list    fetchUserList     返回從外部源獲取的數據
get         get_user_list      getUserList       返回對象或者其他內部數據結構中的數據
make        make_user          makeUser          返回新建對象(不使用new操作符)
on          on_mouseover       onMouseover       事件處理程序。事件應是單字的,和HTML標記一致
save        save_user_list     saveUserList      把數據保存到對象或者其他內部數據結構中
set         set_user_name      setUserName       初始化或者更新通過參數提供的值
store       store_user_list    storeUserList     發送數據到外部源進行存儲,比如通過AJAX調用
update      update_user_list   updateUserList    和set類似,但有“先前己經初始化了”的暗含意思

 【命名未知類型的變量】

  有時候,我們實際上不知道變量包含的數據類型是什么。有兩種情況很常見

  1、編寫多態函數(接收多種數據類型的函數)

  2、接收的數據來自外部數據源,比如AJAX或者Web Socket訂閱

局部作用域     模塊作用域      說明
http_data    httpData      接收自HTTP訂閱的未知數據類型
socket_data  socketData    接收自Web socket的未知數據類型
arg_data     data          通過參數傳遞的未知數據類型

  下面是一個示例

dogPrototype = {
  body_temp_c   : 36.5,
  dog_name      : 'Guido',
  greet_text    : 'Grrrr',
  speak_text    : 'I am a dog',
  height_in_m   : 1.0,
  leg_count     : 4,
  check_destroy : checkDestroy,
  destroy_dog   : destroyDog,
  print_greet   : printGreet,
  print_name    : printName,
  print_speak   : printSpeak,
  show_flash    : showFlash,
  redraw_dog    : redrawDog
};

【變量聲明和賦值】 

  1、創建新對象、映射或者數組的時候,使用{}或者[]]代替new Object()或者new Array()

  2、使用工具方法復制對象和數組

  3、一開始就在函數作用域內,使用單個var關鍵字,顯式地聲明所有的變量

  4、不要使用塊

  5、把所有函數賦給變量。這進一步鞏固了JavaScript把函數當作第一類對象的事實

  6、 當函數需要三個以上的參數時,使用具名參數(named arguments),因為位置參數的含義很容易忘記,並且也不能進行自我說明

  7、每條變量賦值語句占用一行。盡可能按字母或者邏輯來排序。多個聲明可以放在單行上

 

命名空間

  很多早期的JavaScript代碼比較簡單,單獨在一張頁面上使用。這些腳本可以(而且經常就是這么做的)使用全局變量,而不會有什么影響。但是隨着JavaScript應用的蓬勃發展和第三方類庫的普遍使用,別人想要全局變量i的可能性會急劇上升。當兩個代碼庫聲明了相同的全局變量時,地獄之門也隨之打開

  只使用單一的全局函數,把其他所有變量的作用域限制在該函數里面,就可以極大地減少這種問題,如下所示:

var spa = (function ()    {
  // other code here
  var initModule = function ()    {
    console.log( 'hi there');
  };
  return { initModule : initModule };
}());

  這個單一的全局函數(在這個示例中是spa)叫做名字空間。賦給它的函數. 在加載的時候就會執行,當然所有在該函數里面賦值的局部變量,在全局名字空間中是不可訪問的。注意我們讓initModule方法對外可見。所以其他代碼可以調用初始化函數,但它不能訪問其他的東西。並且必須使用spa前綴

spa.initModule();

  可以把命名空間再細分,這樣就不會被迫用單個文件來裝載50KB的應用。比如,可以創建spa、spa.shell和spa.slider這樣的命名空間:

// In the file spa.js: 
var spa = (function () {
  // some code here 
}());
// In the file spa.shell.js: 
var spa.shell = (function () {
  // some code here 
}());
// In the file spa.slider.js: 
var spa.slider = (function () {
  // some code here 
}());

  命名空間是創建可維護的JavaScript代碼的關鍵所在

【文件命名】

  1、根據命名空間來命名JavaScript文件,每個文件一個命名空間示例

spa.js    // spa.*    namespace
spa.shell.js    // spa.shell.* namespace
spa.slider.js // spa.slider.* namespace

  2、為會生成HTML的每個JavaScript文件創建一個CSS文件。示例:

spa.css    // spa.*    namespace
spa.shell.css // spa.shell.* namespace 
spa.slider.css // spa.slider.* namespace

  3、給CSS選擇器加上模塊名前綴。這種做法能極大地有助於避免和第三方模塊的意外沖突

spa.css defines #spa,    .spa-x-clearall
spa.shell.css defines
#spa-shell-header, #spa-shell-footer, .spa-shell-main

 

JS模板

  模塊按一致的區塊來划分,是很有價值的做法。它能幫助我們理解和瀏覽代碼,提醒我們要以良好的方式來編碼

【使用IIFE創建命名空間】

  使用自執行函數為模塊創建命名空間。這能防止意外地創建全局JavaScript變量。每個文件應該只定義一個命名空間,並且文件名正好和命名空間對應。比如,模塊的名字空間是spa.shell,則文件名應為spa.shell.js

spa.module = (function(){

})();

【聲明並初始化模塊作用域變量】

  一般會使用configMap來保存模塊配置、使用stateMap來保存運行時的狀態值以及使用jqueryMap來緩存jQuery集合

var
  configMap = {
    settable_map : { color_name: true }, 
    color_name    : 'blue'
  },
  stateMap = { $container : null }, 
  jqueryMap = {};
  // Begin DOM method /setJqueryMap/
  setJqueryMap = function () {
    var
      $append_target = stateMap.$append_target,
      $slider        = $append_target.find( '.spa-chat' );
    jqueryMap = {
      $slider   : $slider,
      $toggle   : $slider.find( '.spa-chat-head-toggle' ),
      $window   : $(window)
    };
  };
  // End DOM method /setJqueryMap/

【聲明工具方法】

  把所有私有的工具方法聚集在它們自己的區塊里面。這些方法不會操作DOM,因此不需要瀏覽器就行。如方法不是單個模塊的工具施,則應該把它移到共享的工具方法庫里面,比如spa.util.js

//------------------- BEGIN UTILITY METHODS ------------------
// Cross-browser method to set __proto__
//   Newer js engines (v1.8.5+) support Object.create()
hasCreate = !! Object.create;
createObject = function ( arg ){
  if ( ! arg ) { return {}; }
  if ( hasCreate ){ return Object.create( arg ); }
  function obj() {};
  obj.prototype = arg;
  return new obj;
};
//-------------------- END UTILITY METHODS -------------------

【DOM方法】

  把所有私有的DOM方法聚集在它們自己的區塊里面。這些方法會訪問和修改DOM,因此需要瀏覽器才能運行。一個DOM方法的例子是移動CSS sprite。set JqueryMap方法用來緩存jQuery集合

//--------------------- BEGIN DOM METHODS --------------------
// functions used in dogPrototype
printName  = function (){
  this.$name.text( this.dog_name );
};

showFlash = function (){
  this.$bg.css({opacity: 1, display:'block'})
    .fadeOut('slow');
};//---------------------- END DOM METHODS ---------------------

【事件處理】

  把所有的私有事件處理程序聚集在它們自己的區塊里面。這些方法會處理事件,比如按鈕點擊、按下按鍵、瀏覽器容器縮放、或者接收Websocket消息。事件處理程序理一般會調用DOM方法來修改DOM,而不是它們自己直接去修改DOM

//     BEGIN EVENT HANDLERS
  onLogin = function ( event, login_user ) {
    configMap.set_chat_anchor( 'opened' );
  };
  onLogout = function ( event, logout_user ) {
    configMap.set_chat_anchor( 'closed' );
    jqueryMap.$title.text( 'Chat' );
    clearChat();
  };
//     END EVENT HANDLERS

【回調方法】

  把所有的回調方法聚集在它們自己的區塊里面。如果有回調函數,一般把它們放在事件處理程序和公開方法之間。它們是准公開方法,因為它們會被所服務的外部模塊使用

//---------------------- BEGIN CALLBACKS ---------------------
setChatAnchor = function ( position_type ) {
  return changeAnchorPart({ chat : position_type });
};
//----------------------- END CALLBACKS ----------------------

【公開方法】

  把所有的公開方法聚集在它們自己的區塊里面。這些方法是模塊公開接口的部分。如果有的話,該區塊應該包括configModule和initModule

  //------------------- BEGIN PUBLIC METHODS -------------------
  configModule = function ( input_map ) {
    spa.util.setConfigMap({
      input_map    : input_map,
      settable_map : configMap.settable_map,
      config_map   : configMap
    });
    return true;
  };
  initModule = function ( $container ) {
    setJqueryMap( $container );
    $.gevent.subscribe( $container, 'spa-logout',     onLogout     );
    $container
      .bind( 'utap',       onTapNav       )
      .bind( 'uheldend',   onHeldendNav   );
    return true;
  };
  return {
    configModule : configModule,
    initModule   : initModule
  };
  //------------------- END PUBLIC METHODS ---------------------

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM