电竞比分网-中国电竞赛事及体育赛事平台

分享

JavaScript代碼不好讀,不好維護(hù)?你需要改變寫(xiě)代碼的習(xí)慣

 123xyz123 2018-04-27
                  良好的編碼習(xí)慣,這是每個(gè)程序員應(yīng)具備的最基本素質(zhì)。無(wú)論是前端程序員還是后端程序員,都要遵循基本的規(guī)范,減少因代碼混亂而造成難以維護(hù)的局面。要做到不管有多少人共同參與同一個(gè)項(xiàng)目,一定要確保每一行代碼都像是同一個(gè)人編寫(xiě)的。


  提高代碼的可讀性和可維護(hù)性,有一些共同的方法,比如注意代碼格式整齊,縮進(jìn)合理,規(guī)范的命名等等。但有一些方式還是和所使用語(yǔ)言本書(shū)的特性有關(guān)。JavaScript是一種弱類型語(yǔ)言,有著相對(duì)松散的限制,這種特點(diǎn)使得開(kāi)發(fā)者可以更靈活更高效地編寫(xiě)JavaScript代碼。但同時(shí)也存在著一些設(shè)計(jì)上的缺陷,使得開(kāi)發(fā)者很容易編寫(xiě)帶有潛在問(wèn)題的代碼。JavaScript引擎在運(yùn)行這些有潛在問(wèn)題的代碼時(shí)可能并不會(huì)報(bào)錯(cuò)或者警告,所以發(fā)現(xiàn)這些問(wèn)題就變的很困難。這些形式各異、隱含有邏輯錯(cuò)誤的代碼,影響著代碼整體的可讀性和可維護(hù)性。因而,需要使用更嚴(yán)格的編碼規(guī)范,避免出現(xiàn)這些不合規(guī)范的代碼帶來(lái)錯(cuò)誤。如下是提高JavaScript代碼可維護(hù)性的一些最佳實(shí)踐方法


  1. 避免定義全局變量或函數(shù)


  定義全局的變量和函數(shù),會(huì)影響代碼的可維護(hù)性。如果在頁(yè)面中運(yùn)行的JavaScript代碼是在相同的作用域里面,那這就意味著代碼之間的定義存在互相影響的可能。如果在其中一段代碼中定義了全局的變量或函數(shù),則這些全局的變量或函數(shù)在另一段代碼中將會(huì)是透明的,意味著在另一段代碼中可以操作或者覆蓋這些變量或函數(shù)。但很多時(shí)候,這樣的情形并不是設(shè)計(jì)需要,而是誤操作。例如,在項(xiàng)目中的一位開(kāi)發(fā)者定義了如下的全局變量和函數(shù):


  var length = 0;


  function init(){…}


  function action() {…}


  如果另外一個(gè)開(kāi)發(fā)者在不知道已定義這些變量和函數(shù)的情況下,也定義了相同名稱的變量或函數(shù),則后定義的函數(shù)或者方法會(huì)覆蓋之前的定義。在代碼中出現(xiàn)這樣的情形是非常嚴(yán)重的,導(dǎo)致了變量值被重置或者函數(shù)邏輯改變,從而發(fā)生不可預(yù)知的錯(cuò)誤。


  有很多的手段可以解決因?yàn)槎x了全局變量而導(dǎo)致代碼污染的情況。最簡(jiǎn)單的方法是把變量和方法封裝在一個(gè)變量對(duì)象上,使其變成對(duì)象的屬性。例如:


  var myCurrentAction = {


  length: 0,


  init: function(){…},


  action: function(){…}


  }


  這樣基本上可以避免全局變量或方法被覆蓋的情況。但這種方案也有弊端,所有變量和函數(shù)的訪問(wèn)都需要通過(guò)主對(duì)象來(lái)實(shí)現(xiàn)了,比如訪問(wèn)如上的length變量,就需要通過(guò)myCurrentAction.length來(lái)訪問(wèn)。這就增加了代碼的重復(fù)度和代碼編寫(xiě)的繁瑣性。另一種改進(jìn)的方案是把全局的變量包含在一個(gè)局部作用域中,然后在這個(gè)作用域中完成這些變量的定義以及變量使用的邏輯。比如,可以通過(guò)定義一個(gè)匿名函數(shù)實(shí)現(xiàn):


  (function () {


  var length = 0;


  function init(){…}


  function action() {…}


  })();


  所有的邏輯都包含在了這個(gè)立即執(zhí)行的匿名函數(shù)中,形成了一個(gè)獨(dú)立的模塊,最大限度地防止了代碼之間的污染。當(dāng)然,在實(shí)際的業(yè)務(wù)中,模塊之間會(huì)有交互,這時(shí)則可以使用return語(yǔ)句,返回需要公開(kāi)的接口,比如要公開(kāi)上述代碼中的init函數(shù),則如上代碼應(yīng)修改為如下形式:


  var myCurrentAction = (function () {


  var length = 0;


  function init(){…}


  function action() {…}


  return {


  init: init


  }


  })();


  經(jīng)過(guò)如此調(diào)整,外部代碼訪問(wèn)init方法時(shí),就可以調(diào)用myCurrentAction.init了。此方案既巧妙地做到了代碼邏輯的封裝,又公開(kāi)了外部需要訪問(wèn)的接口,是代碼模塊化的最佳實(shí)踐方式之一。


  另外一個(gè)避免定義全局變量的方式是:確保在定義變量時(shí)使用var關(guān)鍵字。如果定義變量時(shí)沒(méi)有使用var,瀏覽器解析時(shí)并不會(huì)報(bào)錯(cuò),而是自動(dòng)把這一變量解析為全局變量,比如如下的代碼就定義了一個(gè)全局的變量length:


  (function () {


  length = 0;


  function init(){…}


  function action() {…}


  })();


  這種可以不通過(guò)var關(guān)鍵字而定義變量的方式也是JavaScript代碼靈活的一種體現(xiàn),但同時(shí)也是代碼中潛在問(wèn)題的根源之一。很多時(shí)候,開(kāi)發(fā)者并非想通過(guò)這種方式定義一個(gè)全局變量,只是錯(cuò)誤地遺漏了var關(guān)鍵字。規(guī)范的制定者也意識(shí)到了這種靈活性帶來(lái)的問(wèn)題,所以在JavaScript代碼的嚴(yán)格模式中,變量定義必須添加var關(guān)鍵字,否則會(huì)報(bào)編譯錯(cuò)誤。


  2. 使用簡(jiǎn)化的編碼方式


  在JavaScript中,提供了很多種簡(jiǎn)化的編碼方式,這些方式保持了代碼的簡(jiǎn)潔性,但同時(shí)也提高了可讀性。如下示例將使用復(fù)雜的方式創(chuàng)建對(duì)象和數(shù)組,這種方式是在后端語(yǔ)言中慣用的方式:


  // 對(duì)象創(chuàng)建


  var person = new Object();


  person.age = 25;


  person.name = 'dang';


  // 數(shù)組創(chuàng)建


  var list = new Array();


  list[0] = 12;


  list[1] = 20;


  list[2] = 24;


  在JavaScript中,可以使用JSON方式創(chuàng)建對(duì)象和數(shù)組。如果開(kāi)發(fā)者熟悉JavaScript,則使用這種方式更簡(jiǎn)潔易讀,代碼如下:


  // 對(duì)象創(chuàng)建


  person = {age: 25, name: 'dang'};


  // 數(shù)組創(chuàng)建


  list = [12, 20, 24];


  3. 使用比較運(yùn)算符===而不是==


  JavaScript有兩組相等的運(yùn)算符:===(嚴(yán)格相等)和!==(嚴(yán)格不等)及==(相等)和!=(不等)。===和!==會(huì)比較兩個(gè)基礎(chǔ)類型值是否相等,或者兩個(gè)復(fù)雜對(duì)象是否指向同一個(gè)地址,而==和!=則會(huì)先進(jìn)行比較值的類型轉(zhuǎn)換,在把兩個(gè)比較值的類型轉(zhuǎn)換為相同類型后才會(huì)進(jìn)行比較運(yùn)算,所以只有在兩個(gè)比較值的類型一致時(shí),它才與第一組相等運(yùn)算符等同。==和!=在比較時(shí)的類型轉(zhuǎn)換規(guī)則也很復(fù)雜,具體如下:


  undefined和null與自己比較時(shí)結(jié)果為true;它們相互比較時(shí)結(jié)果也為true;但與其它類型比較時(shí),結(jié)果為false;原始類型(數(shù)值、布爾和字符類型)進(jìn)行比較時(shí),會(huì)先轉(zhuǎn)換為數(shù)值類型再比較;對(duì)象和原始類型比較時(shí),會(huì)先將對(duì)象轉(zhuǎn)換為原始類型,然后再比較。來(lái)看看相應(yīng)的示例:


  null == undefined; // true


  0 == null; // false


  false == '0' // true


  false == 'false' // false


  '\n 123 \t' == 123 // true


  var p = {toString: function(){ return '1'}}


  p == 1; // true


  要記住如上的這些規(guī)則很難,而使用===和!==這兩個(gè)嚴(yán)格相等運(yùn)算符進(jìn)行比較時(shí)并不存在類型轉(zhuǎn)換的過(guò)程,因此會(huì)返回正確的結(jié)果。為了避免出現(xiàn)隱含的錯(cuò)誤,推薦使用===和!==運(yùn)算符,不要使用==和!=運(yùn)算符。


  4. 避免使用with語(yǔ)句


  在JavaScript中,with語(yǔ)句可用來(lái)快捷地訪問(wèn)對(duì)象的屬性。with語(yǔ)句的格式如下:


  with (object) {


  statement


  }


  with語(yǔ)句的使用原理是:JavaScript解析和運(yùn)行時(shí),會(huì)給with語(yǔ)句單獨(dú)建立了一個(gè)作用域,而和with語(yǔ)句結(jié)合的對(duì)象上的屬性則成為了此作用域的局部變量,因此可以直接訪問(wèn)。比如:


  with (Math) {


  a = PI * r * r;


  x = r * cos(PI);


  y = r * sin(PI / 2);


  }


  上面代碼和如下的代碼會(huì)完成同樣的事情:


  a = Math.PI * r * r;


  x = r * Math.cos(PI);


  y = r * Math.sin(PI / 2);


  從代碼量上看,使用with語(yǔ)句的確簡(jiǎn)化了代碼,但不幸的是,使用with語(yǔ)句可能也會(huì)帶來(lái)不可思議的bug以及兼容問(wèn)題:


  首先,使用with語(yǔ)句,使得代碼難以閱讀,對(duì)于with語(yǔ)句內(nèi)部的變量引用,只有在運(yùn)行時(shí)才能知道變量屬于哪個(gè)對(duì)象。比如:


  function f(x, o) {


  with (o) {


  print(x);


  }


  }


  來(lái)看一下with語(yǔ)句中的x變量,但從代碼分析,x可能是參數(shù)上傳入的x,也可能是o對(duì)象上的屬性o.x,這取決于實(shí)際運(yùn)行時(shí)的上下文。 當(dāng)從代碼無(wú)法確認(rèn)實(shí)際邏輯時(shí),這段代碼就可能會(huì)有潛在的bug。如上的代碼中,可能開(kāi)發(fā)者在代碼中使用x的期望是從參數(shù)傳入x,但如果實(shí)際運(yùn)行時(shí)o對(duì)象上有x屬性,則with語(yǔ)句內(nèi)部的x會(huì)成為o對(duì)象上的x屬性,這就和開(kāi)發(fā)者預(yù)期不同了。


  其次,with語(yǔ)句存在兼容問(wèn)題,如下的示例來(lái)自mozilla開(kāi)發(fā)網(wǎng)站:


  function f(foo, values) {


  with (foo) {


  console.log(values)


  }


  }


  如果在ECMAScript 5環(huán)境中調(diào)用f([1,2,3], obj),則with語(yǔ)句中的values引用的是obj對(duì)象。如果在ECMAScript 6環(huán)境中調(diào)用 f([1,2,3], obj),由于Array.prototype引入了values屬性,因此with語(yǔ)句中的values引用的是[1,2,3].values。


  此外, with語(yǔ)句的設(shè)計(jì)方面也有缺陷,在with語(yǔ)句內(nèi)部修改和with語(yǔ)句結(jié)合的對(duì)象后,并不能同步到with內(nèi)部,即不能保證對(duì)象數(shù)據(jù)的一致性。舉個(gè)例子:


  var group = {


  value: {


  node: 1


  }


  };


  with(group.value) {


  group.value = {


  node: 2


  };


  // 顯示錯(cuò)誤: 1


  console.log(node);


  }


  // 顯示正確: 2


  console.log(group.value.node);


  如上的例子中,在with內(nèi)部修改了group.value對(duì)象,設(shè)置了group.value.node值為2,但在with語(yǔ)句內(nèi)部的node值并沒(méi)用同步修改為2。


  基于以上的分析,在使用with語(yǔ)句的過(guò)程中,開(kāi)發(fā)者通過(guò)閱讀代碼不能知道它將會(huì)做什么,即無(wú)法確定代碼是否會(huì)正確地做期望的事情,并且with語(yǔ)句也存在設(shè)計(jì)上的缺陷,所以應(yīng)該在代碼中避免使用with語(yǔ)句。


  5. 避免使用eval


  在JavaScript中,eval函數(shù)的用法很簡(jiǎn)單,它會(huì)接受一個(gè)字符串參數(shù),把字符串內(nèi)容作為代碼執(zhí)行,并返回執(zhí)行結(jié)果。典型的用法如下:


  eval("x=1;y=2; x*y")


  但這個(gè)函數(shù)存在被濫用的情況。很多新手因?yàn)椴涣私釰avaScript語(yǔ)法,所以會(huì)在某些不恰當(dāng)?shù)膱?chǎng)合使用eval函數(shù)。比如想得到對(duì)象上的屬性值,但由于屬性名是通過(guò)變量傳入的,所以無(wú)法用點(diǎn)操作符,這個(gè)時(shí)候就可能會(huì)想要使用eval,代碼類似如下形式:


  eval('obj.' + key);


  其實(shí)可以使用下標(biāo)法取得屬性值:


  obj[key]


  從eval的功能上看,使用eval函數(shù)會(huì)讓代碼難以閱讀,影響代碼的可維護(hù)性。除此之外,eval的使用也存在安全性問(wèn)題,因?yàn)樗鼤?huì)執(zhí)行任意傳入的代碼,而傳入的代碼有可能是未知的或者來(lái)自不受控制的源,所以盡量避免使用eval。其實(shí)在大多數(shù)的情況下,都是可以使用其它方案來(lái)代替eval的功能。上例便是其中一個(gè)典型的例子,使用下標(biāo)法代替使用eval函數(shù)取得了對(duì)象的屬性。


  和eval函數(shù)類似的還有setTimeout和setInterval函數(shù),這兩個(gè)函數(shù)也可以接受字符串參數(shù),當(dāng)傳入的參數(shù)為字符串時(shí),它們會(huì)做類似eval函數(shù)的處理,把字符串當(dāng)作代碼執(zhí)行。所以使用這兩個(gè)函數(shù)時(shí),應(yīng)該避免使用字符串類型參數(shù)。此外,F(xiàn)unction構(gòu)造器也和eval函數(shù)的功能類似,所以也應(yīng)該避免使用。


  6. 不要編寫(xiě)檢測(cè)瀏覽器的代碼


  經(jīng)常在一些老舊的JavaScript代碼中存在瀏覽器判斷的邏輯,即根據(jù)瀏覽器的不同做不同的處理。這種判斷瀏覽器的做法在五六年以前還算是有一定合理性的,因?yàn)楫?dāng)時(shí)瀏覽器的發(fā)展很緩慢,瀏覽器的功能變化不大。但隨著瀏覽器更新的速度越來(lái)越快,并且瀏覽器之間的差異也越來(lái)越小,原來(lái)這些判斷瀏覽器版本的代碼邏輯就不適時(shí)宜了,甚至有可能導(dǎo)致邏輯上的錯(cuò)誤。因?yàn)闉g覽器之前不支持的功能有可能在新版本中得到了支持,瀏覽器的bug也可能在新版本中得到了修正。這樣一來(lái),之前那些通過(guò)判斷瀏覽器而修正的bug就可能完全沒(méi)有作用了,開(kāi)發(fā)者不得不重新修改代碼來(lái)適應(yīng)新的瀏覽器。所以,最佳的做法是不要編寫(xiě)檢測(cè)瀏覽器的代碼,取而代之的是檢測(cè)瀏覽器是否支持某個(gè)特定功能。開(kāi)發(fā)者可以借助目前流行的Modernizr框架來(lái)檢測(cè)瀏覽器的特性支持。


  當(dāng)然,也存在某些特定情況需要判斷瀏覽器的版本,尤其是判斷IE瀏覽器。這個(gè)時(shí)候,最好是把針對(duì)特定瀏覽器的代碼邏輯放置在單獨(dú)的文件中,方便后期的維護(hù)和移除。比如,已經(jīng)知道IE8及以下版本瀏覽器不支持HTML5的新標(biāo)簽,所以如果要在頁(yè)面上使用HTML5新標(biāo)簽,則需要針對(duì)這些瀏覽器加入兼容代碼。這時(shí),可把兼容代碼放在單獨(dú)的文件中,頁(yè)面中添加如下代碼:

  后期如果頁(yè)面不再支持IE8及以下版本瀏覽器,則只需移除此代碼引用即可。


  jQuery 從 1.9 版開(kāi)始,移除了 $.browser 和$.browser.version ,取而代之的是$.support。jQuery的做法正是為了讓開(kāi)發(fā)者不再借助$.browser和$.browser.version來(lái)判斷瀏覽器版本,而是使用$.support來(lái)判斷瀏覽器的特性支持。             

    本站是提供個(gè)人知識(shí)管理的網(wǎng)絡(luò)存儲(chǔ)空間,所有內(nèi)容均由用戶發(fā)布,不代表本站觀點(diǎn)。請(qǐng)注意甄別內(nèi)容中的聯(lián)系方式、誘導(dǎo)購(gòu)買(mǎi)等信息,謹(jǐn)防詐騙。如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請(qǐng)點(diǎn)擊一鍵舉報(bào)。
    轉(zhuǎn)藏 分享 獻(xiàn)花(0

    0條評(píng)論

    發(fā)表

    請(qǐng)遵守用戶 評(píng)論公約

    類似文章 更多