整合營(yíng)銷服務(wù)商

          電腦端+手機(jī)端+微信端=數(shù)據(jù)同步管理

          免費(fèi)咨詢熱線:

          html實(shí)體編碼遇上js代碼

          html實(shí)體編碼遇上js代碼

          雙引號(hào)

          在js代碼中

          在js中單、雙引號(hào)引起來(lái)的是字符串,如果我們要在字符串中使用單、雙引號(hào),需要反斜杠進(jìn)行轉(zhuǎn)義

          let str='user\'s name';
          // or
          let str=" user's name";
          // or
          let str="she said:\"...\".";
          

          如果在字符串中輸出反斜杠,仍然是用反斜杠轉(zhuǎn)義,即2個(gè)反斜杠輸出1個(gè)反斜杠

          在html代碼中

          html標(biāo)簽中,屬性值通常用雙引號(hào)引起來(lái),也可以使用單引號(hào)或不用引號(hào)。

          <input name=user />
          <input name="user" />
          <input name='user' />
          

          這3種寫法都正確,不過(guò)通常我們是選擇用雙引號(hào)引起來(lái)。

          如果我們要在屬性值中使用單、雙綽號(hào),我們不能直接寫成下面這樣

          <input name=user'name />
          <input name="user"name" />
          <input name='user'name' />
          

          這些全部是錯(cuò)誤的。我們要像在js中對(duì)單、雙引號(hào)轉(zhuǎn)義一樣,對(duì)屬性中的單、雙引號(hào)轉(zhuǎn)義

          在html中輸出預(yù)留符號(hào),可以使用字符實(shí)體轉(zhuǎn)義的形式,這里有簡(jiǎn)單介紹:http://www.w3school.com.cn/html/html_entities.asp。即想輸出一個(gè)雙引號(hào)可以使用"的形式,

          <input name="user"name" />
          

          除此之外,html還支持十進(jìn)制與十六進(jìn)制編碼的形式輸出字符,如我們知道字符a的ascii碼的十進(jìn)制是97 十六進(jìn)制是61

          所以我們?cè)陧?yè)面body中輸出一個(gè)字符a,有以下3種形式

          <body>
           a<!--直接輸出-->
           a<!--十進(jìn)制輸出-->
           a<!--十六進(jìn)制輸出-->
          </body>
          

          同樣,單雙引號(hào)也有十進(jìn)制(單:39,雙:34)與十六進(jìn)制(單:27,雙:22),所以我們?cè)趯傩灾休敵鲆粋€(gè)單引號(hào)有2種選擇,十進(jìn)制與十六進(jìn)制

          <input name='user'name' /><!--十進(jìn)制-->
          <input name='user'name' /><!--十六進(jìn)制-->
          

          而輸出一個(gè)雙引號(hào)則有3種選擇

          <input name="user"name" /><!--實(shí)體-->
          <input name="user"name" /><!--十進(jìn)制-->
          <input name="user"name" /><!--十六進(jìn)制-->
          

          當(dāng)js代碼遇上實(shí)體編碼

          我們可以通過(guò)dom節(jié)點(diǎn)提供的事件寫上調(diào)用js的代碼,如點(diǎn)擊body彈出hello這個(gè)字符串,我們可以寫成

          <body onclick="alert('hello')">
          click here
          </body>
          

          如果我們的需求是就彈出一個(gè)雙引號(hào)呢?

          根據(jù)前述規(guī)則,我們要寫成:

          <body onclick="alert('"')"><!--這里用十進(jìn)制或十六進(jìn)制都可以-->
          click here
          </body>
          

          當(dāng)然,alert里的單引號(hào)也可以使用十進(jìn)制或十六進(jìn)制編碼

          <body onclick="alert("'")"><!--"單引號(hào)  '雙引號(hào)-->
          click here
          </body>
          

          這樣也是可以的。

          是不是有點(diǎn)xss的感覺(jué)?

          如果我們把彈雙引號(hào)的需求改成單引號(hào)呢?

          <body onclick="alert(''')"><!--這樣html中是合法的,但js中并不合法,因?yàn)樵趈s中,中間的單引號(hào)并沒(méi)有轉(zhuǎn)義-->
          click here
          </body>
          

          如果我們用十進(jìn)制或十六進(jìn)制編碼呢?

          <body onclick="alert('"')"><!--這樣可以嗎-->
          click here
          </body>
          

          這樣仍然是不可以的

          我們要對(duì)js字符串中的單引號(hào)進(jìn)行轉(zhuǎn)義,如

          <body onclick="alert('\'')"><!--轉(zhuǎn)義后可正確彈出-->
          click here
          </body>
          

          <body onclick="alert('\"')"><!--轉(zhuǎn)義后可正確彈出-->
          click here
          </body>
          

          前面的onclick="alert('\'')"看起來(lái)還正常,后面的這個(gè)onclick="alert('\"')"就有點(diǎn)不直觀了。因?yàn)楹竺孢@個(gè)看上去反斜杠像在轉(zhuǎn)義&這1個(gè)字符,而&在js的字符串中并不需要轉(zhuǎn)義的。

          動(dòng)態(tài)輸出

          如前述的alert彈出的消息,如果是一個(gè)變量控制,動(dòng)態(tài)輸出呢?

          <body onclick="alert('${msg}')">
          click here
          </body>
          

          那我們這個(gè)msg字符串就得注意了,從這個(gè)示例來(lái)看,這個(gè)動(dòng)態(tài)的msg即出現(xiàn)在屬性onclick中,也出現(xiàn)在alert的單引號(hào)開(kāi)始的字符串中。

          我們要對(duì)msg中的雙引號(hào)轉(zhuǎn)成"或"或",并對(duì)msg中單引號(hào)的前面加上一個(gè)反斜杠\ ?

          題外話:對(duì)msg中的反斜杠需要做double處理,因?yàn)榉葱备茉趆tml屬性中并不是特殊的,但在js的字符串中是特殊的。因此正確的做法是對(duì)反斜杠及單引號(hào)前面各加上一個(gè)反斜杠

          然而,你并不能保證屬性是用雙引號(hào),alert中的字符串用的是單引號(hào),因?yàn)榭梢詫懗上旅孢@樣

          <body onclick='alert("${msg}")'>
          click here
          </body>
          

          ?

          這種情況我們要對(duì)msg中的單引號(hào)轉(zhuǎn)成'或',并對(duì)msg中雙引號(hào)前面加上一個(gè)反斜杠\

          題外話:同上

          看上去要根據(jù)不同的情況做不同的處理,其實(shí)也不需要

          我們只需要對(duì)單、雙引號(hào)前面加上一個(gè)反斜杠\然后再對(duì)單、雙引號(hào)實(shí)體編碼即可。

          在js中如果反斜杠后面跟的不需要反斜杠轉(zhuǎn)義的字符,那么這個(gè)反斜杠是被丟棄的,因此像

          var str="user\'s name";
          

          單引號(hào)前面多加一個(gè)反斜杠也不要緊的。

          自動(dòng)化處理與識(shí)別提醒

          在magix項(xiàng)目中,由于magix-combine的支持,可識(shí)別出屬性中js代碼的部分,并自動(dòng)化處理,如

          <button mx-click="showName({name:'<%=name%>'})">click here</button>
          

          name這個(gè)變量可包含任意的單、雙引號(hào)及反斜杠。工具自動(dòng)識(shí)別并處理,開(kāi)發(fā)者不需要做任何事情。

          而對(duì)于這樣的寫法:

          <button mx-click="showName({name:'"'})">click here</button>
          <!-- or-->
          <button mx-click="showName({name:'\"'})">click here</button>
          

          第一種寫法其實(shí)并不正確,但第二種情況看上去又怪怪的。magix-combine工具能識(shí)別出來(lái)是否需要添加反斜杠,并自動(dòng)添加處理。

          第一種需要添加反斜杠,工具會(huì)自動(dòng)加上,并提醒開(kāi)發(fā)者這里的寫法是不正確的。

          第二種說(shuō)明開(kāi)發(fā)者意識(shí)到了問(wèn)題所在,自己處理了,工具就不再處理也不再提醒開(kāi)發(fā)者。

          么是HTML

          Hyper Text Markup Language, 超文本標(biāo)記語(yǔ)言

          標(biāo)記又稱為標(biāo)簽(Tag), 一般語(yǔ)法:

          <tagName></tagName>

          它可以有屬性(Attribute):

          <tagName attributeName="value">, 如:

          <meta charset="utf-8" />

          標(biāo)簽也可以不成對(duì)地關(guān)閉:

          <tagName />

          HTML文檔由瀏覽器解釋并執(zhí)行。

          HTML文檔基本結(jié)構(gòu)

          <!DOCTYPE html> ----- 告訴瀏覽器用html5的標(biāo)準(zhǔn)來(lái)解釋和執(zhí)行該網(wǎng)頁(yè)

          <html>

          <head> ---- 頭部, 可包含meta, title等標(biāo)簽

          </head>

          <body> ---- 主體, 包含主要內(nèi)容

          </body>

          </html>

          meta

          <meta charset="utf-8" /> 用于告訴瀏覽器用什么樣的字符編碼來(lái)解釋網(wǎng)頁(yè)中的文本.

          常見(jiàn)編碼:

          iso-8859-1: 純英文編碼

          gbk, gb2312: 簡(jiǎn)體中文編碼

          big5: 大五碼,繁體中文編碼,主要應(yīng)用于臺(tái)灣地區(qū)

          utf-8: 國(guó)際首選編碼,它兼容所有的字符

          除此之外, meta還可以通過(guò)keywords, description屬性對(duì)頁(yè)面關(guān)鍵詞及描述信息進(jìn)行設(shè)置, 以提高搜索引擎的命中.

          title

          網(wǎng)頁(yè)標(biāo)題, 顯示在瀏覽器選項(xiàng)卡的標(biāo)題欄上!

          文本排版標(biāo)簽

          h1-h6: 內(nèi)容標(biāo)題標(biāo)簽

          p: 段落

          br: 換行

          hr: 水平線

          strong: 粗體文本

          em: 斜體文本

          span: 無(wú)任何特殊樣式的文本

          pre: 預(yù)格式標(biāo)簽,其中的內(nèi)容在頁(yè)面上帶格式渲染

          small: 比當(dāng)前字體小的文本

          html特殊字符/轉(zhuǎn)義字符

          空格

          < 小于

          > 大于

          ? 版權(quán)符

          " 雙引號(hào)

          html注釋

          <!-- 注釋內(nèi)容 -->

          圖像標(biāo)簽

          <img

          src="圖像地址"

          title="鼠標(biāo)懸停提示"

          alt="圖像加載錯(cuò)誤時(shí)的替代文本"

          width="寬度"

          height="高度"

          />

          圖像地址分為2種:

          1. 相對(duì)地址, 如: img/cc.jpg

          2. 絕對(duì)地址, 如: http://img.bcd.com/2017/1644232421.jpg

          超鏈接


          <a href="鏈接地址" target="目標(biāo)窗口">文本|圖片</a>

          目標(biāo)窗口:

          _self: 目標(biāo)頁(yè)面在當(dāng)前窗口打開(kāi)

          _blank: 目標(biāo)頁(yè)面在新窗口中打開(kāi)

          如果是在頁(yè)面具有frameset/frame/iframe的場(chǎng)景下:

          _top: 在頂級(jí)窗口中打開(kāi)

          _parent: 在父級(jí)窗口中打開(kāi)

          _自定義名稱: 在指定的特定窗口中打開(kāi)

          三種用法:

          1. 頁(yè)面間鏈接

          <a href="page/login.html"></a>

          2. 錨鏈接

          <a href="#help"></a>

          help是本頁(yè)面中一處id為help的標(biāo)簽, 如: <p id="help">

          或者:

          help是通過(guò)a標(biāo)簽命名的錨記, 如: <a name="help"></a>

          3. 功能性鏈接

          喚醒本地安裝的外部程序如 outlook/foxmail/qq/msn/aliwangwang...

          <a href="mailto:abcdef@qq.com"></a>

          div標(biāo)簽

          div是一個(gè)容器, 常用于頁(yè)面的布局

          標(biāo)簽的分類:

          1. 塊級(jí)標(biāo)簽/塊級(jí)元素

          如: div, h1-h6, p, hr

          特征: 獨(dú)占容器中的一行, 其寬度是容器的100%

          2. 行級(jí)標(biāo)簽/行級(jí)元素

          如: span, img, strong, em, a

          特征1: 多個(gè)行級(jí)元素可以同處一行, 其寬度由內(nèi)容來(lái)?yè)伍_(kāi)(auto)

          特征2: 大部分行級(jí)元素設(shè)置其width/height無(wú)效

          HBuilder常用快捷鍵

          ctrl + D : 刪除當(dāng)前行

          ctrl + PgUp : 當(dāng)前行上移

          ctrl + PgDown : 當(dāng)前行下移

          ctrl + / : 注釋 | 取消注釋

          ctrl + shift + F : 整理代碼格式

          ctrl + C : 復(fù)制當(dāng)前行

          ctrl + X : 剪切當(dāng)前行

          ctrl + V : 粘貼

          ctrl + Z : 撤消上一步操作

          ctrl + S : 保存當(dāng)前文件

          ctrl + shift + S : 保存項(xiàng)目中全部文件

          ctrl + Enter : 在當(dāng)前行的下方插入新行

          ctrl + shift + Enter : 在當(dāng)前行的上方插入新行


          以上知識(shí)能做的效果圖

          部分效果

          前言

          JavaScript在百度一直有著廣泛的應(yīng)用,特別是在瀏覽器端的行為管理。本文檔的目標(biāo)是使JavaScript代碼風(fēng)格保持一致,容易被理解和被維護(hù)。

          雖然本文檔是針對(duì)JavaScript設(shè)計(jì)的,但是在使用各種JavaScript的預(yù)編譯語(yǔ)言時(shí)(如TypeScript等)時(shí),適用的部分也應(yīng)盡量遵循本文檔的約定。

          2 代碼風(fēng)格

          2.1 文件

          [建議] JavaScript 文件使用無(wú) BOM 的 UTF-8 編碼。

          解釋:UTF-8 編碼具有更廣泛的適應(yīng)性。BOM 在使用程序或工具處理文件時(shí)可能造成不必要的干擾。

          [建議] 在文件結(jié)尾處,保留一個(gè)空行。

          2.2 結(jié)構(gòu)

          [強(qiáng)制] 使用 4 個(gè)空格做為一個(gè)縮進(jìn)層級(jí),不允許使用 2 個(gè)空格 或 tab 字符。

          [強(qiáng)制] switch 下的 case 和 default 必須增加一個(gè)縮進(jìn)層級(jí)。

          // good
          switch (variable) {
           case '1':
           // do...
           break;
           case '2':
           // do...
           break;
           default:
           // do...
          }
          // bad
          switch (variable) {
          case '1':
           // do...
           break;
          case '2':
           // do...
           break;
          default:
           // do...
          }
          

          [強(qiáng)制] 二元運(yùn)算符兩側(cè)必須有一個(gè)空格,一元運(yùn)算符與操作對(duì)象之間不允許有空格。

          var a=!arr.length;
          a++;
          a=b + c;
          

          [強(qiáng)制] 用作代碼塊起始的左花括號(hào) { 前必須有一個(gè)空格。

          示例:

          // good
          if (condition) {
          }
          while (condition) {
          }
          function funcName() {
          }
          // bad
          if (condition){
          }
          while (condition){
          }
          function funcName(){
          }
          

          [強(qiáng)制] if / else / for / while / function / switch / do / try / catch / finally 關(guān)鍵字后,必須有一個(gè)空格。

          // good
          if (condition) {
          }
          while (condition) {
          }
          (function () {
          })();
          // bad
          if(condition) {
          }
          while(condition) {
          }
          (function() {
          })();
          

          [強(qiáng)制] 在對(duì)象創(chuàng)建時(shí),屬性中的 : 之后必須有空格,: 之前不允許有空格。

          // good
          var obj={
           a: 1,
           b: 2,
           c: 3
          };
          // bad
          var obj={
           a : 1,
           b:2,
           c :3
          };
          

          [強(qiáng)制] 函數(shù)聲明、具名函數(shù)表達(dá)式、函數(shù)調(diào)用中,函數(shù)名和 ( 之間不允許有空格。

          // good
          function funcName() {
          }
          var funcName=function funcName() {
          };
          funcName();
          // bad
          function funcName () {
          }
          var funcName=function funcName () {
          };
          funcName ();
          

          [強(qiáng)制] , 和 ; 前不允許有空格。

          // good
          callFunc(a, b);
          // bad
          callFunc(a , b) ;
          

          [強(qiáng)制] 在函數(shù)調(diào)用、函數(shù)聲明、括號(hào)表達(dá)式、屬性訪問(wèn)、if / for / while / switch / catch 等語(yǔ)句中,() 和 [] 內(nèi)緊貼括號(hào)部分不允許有空格。

          // good
          callFunc(param1, param2, param3);
          save(this.list[this.indexes[i]]);
          needIncream && (variable +=increament);
          if (num > list.length) {
          }
          while (len--) {
          }
          // bad
          callFunc( param1, param2, param3 );
          save( this.list[ this.indexes[ i ] ] );
          needIncreament && ( variable +=increament );
          if ( num > list.length ) {
          }
          while ( len-- ) {
          }
          

          [強(qiáng)制] 單行聲明的數(shù)組與對(duì)象,如果包含元素,{} 和 [] 內(nèi)緊貼括號(hào)部分不允許包含空格。

          解釋:聲明包含元素的數(shù)組與對(duì)象,只有當(dāng)內(nèi)部元素的形式較為簡(jiǎn)單時(shí),才允許寫在一行。元素復(fù)雜的情況,還是應(yīng)該換行書寫。

          // good
          var arr1=[];
          var arr2=[1, 2, 3];
          var obj1={};
          var obj2={name: 'obj'};
          var obj3={
           name: 'obj',
           age: 20,
           sex: 1
          };
          // bad
          var arr1=[ ];
          var arr2=[ 1, 2, 3 ];
          var obj1={ };
          var obj2={ name: 'obj' };
          var obj3={name: 'obj', age: 20, sex: 1};
          

          [強(qiáng)制] 行尾不得有多余的空格。

          [強(qiáng)制] 每個(gè)獨(dú)立語(yǔ)句結(jié)束后必須換行。

          [強(qiáng)制] 每行不得超過(guò) 120 個(gè)字符。

          解釋:超長(zhǎng)的不可分割的代碼允許例外,比如復(fù)雜的正則表達(dá)式。長(zhǎng)字符串不在例外之列。

          [強(qiáng)制] 運(yùn)算符處換行時(shí),運(yùn)算符必須在新行的行首。

          // good
          if (user.isAuthenticated()
           && user.isInRole('admin')
           && user.hasAuthority('add-admin')
           || user.hasAuthority('delete-admin')
          ) {
           // Code
          }
          var result=number1 + number2 + number3
           + number4 + number5;
          // bad
          if (user.isAuthenticated() &&
           user.isInRole('admin') &&
           user.hasAuthority('add-admin') ||
           user.hasAuthority('delete-admin')) {
           // Code
          }
          var result=number1 + number2 + number3 +
           number4 + number5;
          

          [強(qiáng)制] 在函數(shù)聲明、函數(shù)表達(dá)式、函數(shù)調(diào)用、對(duì)象創(chuàng)建、數(shù)組創(chuàng)建、for語(yǔ)句等場(chǎng)景中,不允許在 , 或 ; 前換行。

          // good
          var obj={
           a: 1,
           b: 2,
           c: 3
          };
          foo(
           aVeryVeryLongArgument,
           anotherVeryLongArgument,
           callback
          );
          // bad
          var obj={
           a: 1
           , b: 2
           , c: 3
          };
          foo(
           aVeryVeryLongArgument
           , anotherVeryLongArgument
           , callback
          );
          

          [建議] 不同行為或邏輯的語(yǔ)句集,使用空行隔開(kāi),更易閱讀。

          // 僅為按邏輯換行的示例,不代表setStyle的最優(yōu)實(shí)現(xiàn)
          function setStyle(element, property, value) {
           if (element==null) {
           return;
           }
           element.style[property]=value;
          }
          

          [建議] 在語(yǔ)句的行長(zhǎng)度超過(guò) 120 時(shí),根據(jù)邏輯條件合理縮進(jìn)。

          // 較復(fù)雜的邏輯條件組合,將每個(gè)條件獨(dú)立一行,邏輯運(yùn)算符放置在行首進(jìn)行分隔,或?qū)⒉糠诌壿嫲催壿嫿M合進(jìn)行分隔。
          // 建議最終將右括號(hào) ) 與左大括號(hào) { 放在獨(dú)立一行,保證與 if 內(nèi)語(yǔ)句塊能容易視覺(jué)辨識(shí)。
          if (user.isAuthenticated()
           && user.isInRole('admin')
           && user.hasAuthority('add-admin')
           || user.hasAuthority('delete-admin')
          ) {
           // Code
          }
          // 按一定長(zhǎng)度截?cái)嘧址⑹褂?+ 運(yùn)算符進(jìn)行連接。
          // 分隔字符串盡量按語(yǔ)義進(jìn)行,如不要在一個(gè)完整的名詞中間斷開(kāi)。
          // 特別的,對(duì)于HTML片段的拼接,通過(guò)縮進(jìn),保持和HTML相同的結(jié)構(gòu)。
          var html='' // 此處用一個(gè)空字符串,以便整個(gè)HTML片段都在新行嚴(yán)格對(duì)齊
           + '<article>'
           + '<h1>Title here</h1>'
           + '<p>This is a paragraph</p>'
           + '<footer>Complete</footer>'
           + '</article>';
          // 也可使用數(shù)組來(lái)進(jìn)行拼接,相對(duì) + 更容易調(diào)整縮進(jìn)。
          var html=[
           '<article>',
           '<h1>Title here</h1>',
           '<p>This is a paragraph</p>',
           '<footer>Complete</footer>',
           '</article>'
          ];
          html=html.join('');
          // 當(dāng)參數(shù)過(guò)多時(shí),將每個(gè)參數(shù)獨(dú)立寫在一行上,并將結(jié)束的右括號(hào) ) 獨(dú)立一行。
          // 所有參數(shù)必須增加一個(gè)縮進(jìn)。
          foo(
           aVeryVeryLongArgument,
           anotherVeryLongArgument,
           callback
          );
          // 也可以按邏輯對(duì)參數(shù)進(jìn)行組合。
          // 最經(jīng)典的是baidu.format函數(shù),調(diào)用時(shí)將參數(shù)分為“模板”和“數(shù)據(jù)”兩塊
          baidu.format(
           dateFormatTemplate,
           year, month, date, hour, minute, second
          );
          // 當(dāng)函數(shù)調(diào)用時(shí),如果有一個(gè)或以上參數(shù)跨越多行,應(yīng)當(dāng)每一個(gè)參數(shù)獨(dú)立一行。
          // 這通常出現(xiàn)在匿名函數(shù)或者對(duì)象初始化等作為參數(shù)時(shí),如setTimeout函數(shù)等。
          setTimeout(
           function () {
           alert('hello');
           },
           200
          );
          order.data.read(
           'id=' + me.model.id, 
           function (data) {
           me.attchToModel(data.result);
           callback();
           }, 
           300
          );
          // 鏈?zhǔn)秸{(diào)用較長(zhǎng)時(shí)采用縮進(jìn)進(jìn)行調(diào)整。
          $('#items')
           .find('.selected')
           .highlight()
           .end();
          // 三元運(yùn)算符由3部分組成,因此其換行應(yīng)當(dāng)根據(jù)每個(gè)部分的長(zhǎng)度不同,形成不同的情況。
          var result=thisIsAVeryVeryLongCondition
           ? resultA : resultB;
          var result=condition
           ? thisIsAVeryVeryLongResult
           : resultB;
          // 數(shù)組和對(duì)象初始化的混用,嚴(yán)格按照每個(gè)對(duì)象的 { 和結(jié)束 } 在獨(dú)立一行的風(fēng)格書寫。
          var array=[
           {
           // ...
           },
           {
           // ...
           }
          ];
          

          [建議] 對(duì)于 if...else...、try...catch...finally 等語(yǔ)句,推薦使用在 } 號(hào)后添加一個(gè)換行 的風(fēng)格,使代碼層次結(jié)構(gòu)更清晰,閱讀性更好。

          if (condition) {
           // some statements;
          }
          else {
           // some statements;
          }
          try {
           // some statements;
          }
          catch (ex) {
           // some statements;
          }
          

          [強(qiáng)制] 不得省略語(yǔ)句結(jié)束的分號(hào)。

          [強(qiáng)制] 在 if / else / for / do / while 語(yǔ)句中,即使只有一行,也不得省略塊 {...}。

          // good
          if (condition) {
           callFunc();
          }
          // bad
          if (condition) callFunc();
          if (condition)
           callFunc();
          

          [強(qiáng)制] 函數(shù)定義結(jié)束不允許添加分號(hào)。

          // good
          function funcName() {
          }
          // bad
          function funcName() {
          };
          // 如果是函數(shù)表達(dá)式,分號(hào)是不允許省略的。
          var funcName=function () {
          };
          

          [強(qiáng)制] IIFE 必須在函數(shù)表達(dá)式外添加 (,非 IIFE 不得在函數(shù)表達(dá)式外添加 (。

          解釋:IIFE=Immediately-Invoked Function Expression.

          額外的 ( 能夠讓代碼在閱讀的一開(kāi)始就能判斷函數(shù)是否立即被調(diào)用,進(jìn)而明白接下來(lái)代碼的用途。而不是一直拖到底部才恍然大悟。

          // good
          var task=(function () {
           // Code
           return result;
          })();
          var func=function () {
          };
          // bad
          var task=function () {
           // Code
           return result;
          }();
          var func=(function () {
          });
          

          2.3 命名

          下面提到的 Camel命名法:駝峰命名法;Pascal命名法:帕斯卡命名法,又叫大駝峰命名法。

          [強(qiáng)制] 變量 使用 Camel命名法。

          var loadingModules={};
          

          [強(qiáng)制] 常量 使用 全部字母大寫,單詞間下劃線分隔 的命名方式。

          var HTML_ENTITY={};
          

          [強(qiáng)制] 函數(shù) 使用 Camel命名法。

          function stringFormat(source) {
          }
          

          [強(qiáng)制] 函數(shù)的 參數(shù) 使用 Camel命名法。

          function hear(theBells) {
          }
          

          [強(qiáng)制] 類 使用 Pascal命名法。

          function TextNode(options) {
          }
          

          [強(qiáng)制] 類的 方法 / 屬性 使用 Camel命名法。

          function TextNode(value, engine) {
           this.value=value;
           this.engine=engine;
          }
          TextNode.prototype.clone=function () {
           return this;
          };
          

          [強(qiáng)制] 枚舉變量 使用 Pascal命名法,枚舉的屬性 使用 全部字母大寫,單詞間下劃線分隔 的命名方式。

          var TargetState={
           READING: 1,
           READED: 2,
           APPLIED: 3,
           READY: 4
          };
          

          [強(qiáng)制] 命名空間 使用 Camel命名法。

          equipments.heavyWeapons={};
          

          [強(qiáng)制] 由多個(gè)單詞組成的縮寫詞,在命名中,根據(jù)當(dāng)前命名法和出現(xiàn)的位置,所有字母的大小寫與首字母的大小寫保持一致。

          function XMLParser() {
          }
          function insertHTML(element, html) {
          }
          var httpRequest=new HTTPRequest();
          

          [強(qiáng)制] 類名 使用 名詞。

          function Engine(options) {
          }
          

          [建議] 函數(shù)名 使用 動(dòng)賓短語(yǔ)。

          function getStyle(element) {
          }
          

          [建議] boolean 類型的變量使用 is 或 has 開(kāi)頭。

          var isReady=false;
          var hasMoreCommands=false;
          

          [建議] Promise對(duì)象 用 動(dòng)賓短語(yǔ)的進(jìn)行時(shí) 表達(dá)。

          var loadingData=ajax.get('url');
          loadingData.then(callback);
          

          2.4 注釋

          2.4.1 單行注釋

          [強(qiáng)制] 必須獨(dú)占一行。// 后跟一個(gè)空格,縮進(jìn)與下一行被注釋說(shuō)明的代碼一致。

          2.4.2 多行注釋

          [建議] 避免使用 /*...*/ 這樣的多行注釋。有多行注釋內(nèi)容時(shí),使用多個(gè)單行注釋。

          2.4.3 文檔化注釋

          [強(qiáng)制] 為了便于代碼閱讀和自文檔化,以下內(nèi)容必須包含以 /**...*/ 形式的塊注釋中。

          解釋:

          1. 文件
          2. namespace
          3. 函數(shù)或方法
          4. 類屬性
          5. 事件
          6. 全局變量
          7. 常量
          8. AMD 模塊

          [強(qiáng)制] 文檔注釋前必須空一行。

          [建議] 自文檔化的文檔說(shuō)明 what,而不是 how。

          2.4.4 類型定義

          [強(qiáng)制] 類型定義都是以{開(kāi)始, 以}結(jié)束。

          解釋:常用類型如:{string}, {number}, {boolean}, {Object}, {Function}, {RegExp}, {Array}, {Date}。

          類型不僅局限于內(nèi)置的類型,也可以是自定義的類型。比如定義了一個(gè)類 Developer,就可以使用它來(lái)定義一個(gè)參數(shù)和返回值的類型。

          [強(qiáng)制] 對(duì)于基本類型 {string}, {number}, {boolean},首字母必須小寫。

          類型定義 語(yǔ)法示例 解釋 String {string} -- Number {number} -- Boolean {boolean} -- Object {Object} -- Function {Function} -- RegExp {RegExp} -- Array {Array} -- Date {Date} -- 單一類型集合 {Array.<string>} string 類型的數(shù)組 多類型 {(number|boolean)} 可能是 number 類型, 也可能是 boolean 類型 允許為null {?number} 可能是 number, 也可能是 null 不允許為null {!Object} Object 類型, 但不是 null Function類型 {function(number, boolean)} 函數(shù), 形參類型 Function帶返回值 {function(number, boolean):string} 函數(shù), 形參, 返回值類型 參數(shù)可選 @param {string=} name 可選參數(shù),=為類型后綴 可變參數(shù) @param {...number} args 變長(zhǎng)參數(shù), ...為類型前綴 任意類型 {*} 任意類型 可選任意類型 @param {*=} name 可選參數(shù),類型不限 可變?nèi)我忸愋?@param {...*} args 變長(zhǎng)參數(shù),類型不限 2.4.5 文件注釋

          [強(qiáng)制] 文件頂部必須包含文件注釋,用 @file 標(biāo)識(shí)文件說(shuō)明。

          /**
           * @file Describe the file
           */
          

          [建議] 文件注釋中可以用 @author 標(biāo)識(shí)開(kāi)發(fā)者信息。

          解釋:

          開(kāi)發(fā)者信息能夠體現(xiàn)開(kāi)發(fā)人員對(duì)文件的貢獻(xiàn),并且能夠讓遇到問(wèn)題或希望了解相關(guān)信息的人找到維護(hù)人。通常情況文件在被創(chuàng)建時(shí)標(biāo)識(shí)的是創(chuàng)建者。隨著項(xiàng)目的進(jìn)展,越來(lái)越多的人加入,參與這個(gè)文件的開(kāi)發(fā),新的作者應(yīng)該被加入 @author 標(biāo)識(shí)。

          @author 標(biāo)識(shí)具有多人時(shí),原則是按照 責(zé)任 進(jìn)行排序。通常的說(shuō)就是如果有問(wèn)題,就是找第一個(gè)人應(yīng)該比找第二個(gè)人有效。比如文件的創(chuàng)建者由于各種原因,模塊移交給了其他人或其他團(tuán)隊(duì),后來(lái)因?yàn)樾略鲂枨螅渌嗽谛略龃a時(shí),添加 @author 標(biāo)識(shí)應(yīng)該把自己的名字添加在創(chuàng)建人的前面。

          @author 中的名字不允許被刪除。任何勞動(dòng)成果都應(yīng)該被尊重。

          業(yè)務(wù)項(xiàng)目中,一個(gè)文件可能被多人頻繁修改,并且每個(gè)人的維護(hù)時(shí)間都可能不會(huì)很長(zhǎng),不建議為文件增加 @author 標(biāo)識(shí)。通過(guò)版本控制系統(tǒng)追蹤變更,按業(yè)務(wù)邏輯單元確定模塊的維護(hù)責(zé)任人,通過(guò)文檔與wiki跟蹤和查詢,是更好的責(zé)任管理方式。

          對(duì)于業(yè)務(wù)邏輯無(wú)關(guān)的技術(shù)型基礎(chǔ)項(xiàng)目,特別是開(kāi)源的公共項(xiàng)目,應(yīng)使用 @author 標(biāo)識(shí)。

          /**
           * @file Describe the file
           * @author author-name(mail-name@domain.com)
           * author-name2(mail-name2@domain.com)
           */
          

          2.4.6 命名空間注釋

          [建議] 命名空間使用 @namespace 標(biāo)識(shí)。

          /**
           * @namespace
           */
          var util={};
          

          2.4.7 類注釋

          [建議] 使用 @class 標(biāo)記類或構(gòu)造函數(shù)。

          解釋:對(duì)于使用對(duì)象 constructor 屬性來(lái)定義的構(gòu)造函數(shù),可以使用 @constructor 來(lái)標(biāo)記。

          /**
           * 描述
           *
           * @class
           */
          function Developer() {
           // constructor body
          }
          

          [建議] 使用 @extends 標(biāo)記類的繼承信息。

          /**
           * 描述
           *
           * @class
           * @extends Developer
           */
          function Fronteer() {
           Developer.call(this);
           // constructor body
          }
          util.inherits(Fronteer, Developer);
          

          [強(qiáng)制] 使用包裝方式擴(kuò)展類成員時(shí), 必須通過(guò) @lends 進(jìn)行重新指向。

          解釋:沒(méi)有 @lends 標(biāo)記將無(wú)法為該類生成包含擴(kuò)展類成員的文檔。

          /**
           * 類描述
           *
           * @class
           * @extends Developer
           */
          function Fronteer() {
           Developer.call(this);
           // constructor body
          }
          util.extend(
           Fronteer.prototype,
           /** @lends Fronteer.prototype */{
           _getLevel: function () {
           // TODO
           }
           }
          );
          

          [強(qiáng)制] 類的屬性或方法等成員信息使用 @public / @protected / @private 中的任意一個(gè),指明可訪問(wèn)性。

          解釋:生成的文檔中將有可訪問(wèn)性的標(biāo)記,避免用戶直接使用非 public 的屬性或方法。

          /**
           * 類描述
           *
           * @class
           * @extends Developer
           */
          var Fronteer=function () {
           Developer.call(this);
           /**
           * 屬性描述
           *
           * @type {string}
           * @private
           */
           this._level='T12';
           // constructor body
          };
          util.inherits(Fronteer, Developer);
          /**
           * 方法描述
           *
           * @private
           * @return {string} 返回值描述
           */
          Fronteer.prototype._getLevel=function () {
          };
          

          2.4.8 函數(shù)/方法注釋

          [強(qiáng)制] 函數(shù)/方法注釋必須包含函數(shù)說(shuō)明,有參數(shù)和返回值時(shí)必須使用注釋標(biāo)識(shí)。

          [強(qiáng)制] 參數(shù)和返回值注釋必須包含類型信息和說(shuō)明。

          [建議] 當(dāng)函數(shù)是內(nèi)部函數(shù),外部不可訪問(wèn)時(shí),可以使用 @inner 標(biāo)識(shí)。

          /**
           * 函數(shù)描述
           *
           * @param {string} p1 參數(shù)1的說(shuō)明
           * @param {string} p2 參數(shù)2的說(shuō)明,比較長(zhǎng)
           * 那就換行了.
           * @param {number=} p3 參數(shù)3的說(shuō)明(可選)
           * @return {Object} 返回值描述
           */
          function foo(p1, p2, p3) {
           var p3=p3 || 10;
           return {
           p1: p1,
           p2: p2,
           p3: p3
           };
          }
          

          [強(qiáng)制] 對(duì) Object 中各項(xiàng)的描述, 必須使用 @param 標(biāo)識(shí)。

          /**
           * 函數(shù)描述
           *
           * @param {Object} option 參數(shù)描述
           * @param {string} option.url option項(xiàng)描述
           * @param {string=} option.method option項(xiàng)描述,可選參數(shù)
           */
          function foo(option) {
           // TODO
          }
          

          [建議] 重寫父類方法時(shí), 應(yīng)當(dāng)添加 @override 標(biāo)識(shí)。如果重寫的形參個(gè)數(shù)、類型、順序和返回值類型均未發(fā)生變化,可省略 @param、@return,僅用 @override 標(biāo)識(shí),否則仍應(yīng)作完整注釋。

          解釋:簡(jiǎn)而言之,當(dāng)子類重寫的方法能直接套用父類的方法注釋時(shí)可省略對(duì)參數(shù)與返回值的注釋。

          2.4.9 事件注釋

          [強(qiáng)制] 必須使用 @event 標(biāo)識(shí)事件,事件參數(shù)的標(biāo)識(shí)與方法描述的參數(shù)標(biāo)識(shí)相同。

          /**
           * 值變更時(shí)觸發(fā)
           *
           * @event
           * @param {Object} e e描述
           * @param {string} e.before before描述
           * @param {string} e.after after描述
           */
          onchange: function (e) {
          }
          

          [強(qiáng)制] 在會(huì)廣播事件的函數(shù)前使用 @fires 標(biāo)識(shí)廣播的事件,在廣播事件代碼前使用 @event 標(biāo)識(shí)事件。

          [建議] 對(duì)于事件對(duì)象的注釋,使用 @param 標(biāo)識(shí),生成文檔時(shí)可讀性更好。

          /**
           * 點(diǎn)擊處理
           *
           * @fires Select#change
           * @private
           */
          Select.prototype.clickHandler=function () {
           /**
           * 值變更時(shí)觸發(fā)
           *
           * @event Select#change
           * @param {Object} e e描述
           * @param {string} e.before before描述
           * @param {string} e.after after描述
           */
           this.fire(
           'change',
           {
           before: 'foo',
           after: 'bar'
           }
           );
          };
          

          2.4.10 常量注釋

          [強(qiáng)制] 常量必須使用 @const 標(biāo)記,并包含說(shuō)明和類型信息。

          /**
           * 常量說(shuō)明
           *
           * @const
           * @type {string}
           */
          var REQUEST_URL='myurl.do';
          

          2.4.11 復(fù)雜類型注釋

          [建議] 對(duì)于類型未定義的復(fù)雜結(jié)構(gòu)的注釋,可以使用 @typedef 標(biāo)識(shí)來(lái)定義。

          // `namespaceA~` 可以換成其它 namepaths 前綴,目的是為了生成文檔中能顯示 `@typedef` 定義的類型和鏈接。
          /**
           * 服務(wù)器
           *
           * @typedef {Object} namespaceA~Server
           * @property {string} host 主機(jī)
           * @property {number} port 端口
           */
          /**
           * 服務(wù)器列表
           *
           * @type {Array.<namespaceA~Server>}
           */
          var servers=[
           {
           host: '1.2.3.4',
           port: 8080
           },
           {
           host: '1.2.3.5',
           port: 8081
           }
          ];
          

          2.4.12 AMD 模塊注釋

          [強(qiáng)制] AMD 模塊使用 @module 或 @exports 標(biāo)識(shí)。

          解釋:@exports 與 @module 都可以用來(lái)標(biāo)識(shí)模塊,區(qū)別在于 @module 可以省略模塊名稱。而只使用 @exports 時(shí)在 namepaths 中可以省略 module: 前綴。

          define(
           function (require) {
           /**
           * foo description
           *
           * @exports Foo
           */
           var foo={
           // TODO
           };
           /**
           * baz description
           *
           * @return {boolean} return description
           */
           foo.baz=function () {
           // TODO
           };
           return foo;
           }
          );
          

          也可以在 exports 變量前使用 @module 標(biāo)識(shí):

          define(
           function (require) {
           /**
           * module description.
           *
           * @module foo
           */
           var exports={};
           /**
           * bar description
           *
           */
           exports.bar=function () {
           // TODO
           };
           return exports;
           }
          );
          

          如果直接使用 factory 的 exports 參數(shù),還可以:

          /**
           * module description.
           *
           * @module
           */
          define(
           function (require, exports) {
           /**
           * bar description
           *
           */
           exports.bar=function () {
           // TODO
           };
           return exports;
           }
          );
          

          [強(qiáng)制] 對(duì)于已使用 @module 標(biāo)識(shí)為 AMD模塊 的引用,在 namepaths 中必須增加 module: 作前綴。

          解釋:namepaths 沒(méi)有 module: 前綴時(shí),生成的文檔中將無(wú)法正確生成鏈接。

          /**
           * 點(diǎn)擊處理
           *
           * @fires module:Select#change
           * @private
           */
          Select.prototype.clickHandler=function () {
           /**
           * 值變更時(shí)觸發(fā)
           *
           * @event module:Select#change
           * @param {Object} e e描述
           * @param {string} e.before before描述
           * @param {string} e.after after描述
           */
           this.fire(
           'change',
           {
           before: 'foo',
           after: 'bar'
           }
           );
          };
          

          [建議] 對(duì)于類定義的模塊,可以使用 @alias 標(biāo)識(shí)構(gòu)建函數(shù)。

          /**
           * A module representing a jacket.
           * @module jacket
           */
          define(
           function () {
           /**
           * @class
           * @alias module:jacket
           */
           var Jacket=function () {
           };
           return Jacket;
           }
          );
          

          [建議] 多模塊定義時(shí),可以使用 @exports 標(biāo)識(shí)各個(gè)模塊。

          // one module
          define('html/utils',
           /**
           * Utility functions to ease working with DOM elements.
           * @exports html/utils
           */
           function () {
           var exports={
           };
           return exports;
           }
          );
          // another module
          define('tag',
           /** @exports tag */
           function () {
           var exports={
           };
           return exports;
           }
          );
          

          [建議] 對(duì)于 exports 為 Object 的模塊,可以使用@namespace標(biāo)識(shí)。

          解釋:使用 @namespace 而不是 @module 或 @exports 時(shí),對(duì)模塊的引用可以省略 module: 前綴。

          [建議] 對(duì)于 exports 為類名的模塊,使用 @class 和 @exports 標(biāo)識(shí)。

          // 只使用 @class Bar 時(shí),類方法和屬性都必須增加 @name Bar#methodName 來(lái)標(biāo)識(shí),與 @exports 配合可以免除這一麻煩,并且在引用時(shí)可以省去 module: 前綴。
          // 另外需要注意類名需要使用 var 定義的方式。
          /**
           * Bar description
           *
           * @see foo
           * @exports Bar
           * @class
           */
          var Bar=function () {
           // TODO
          };
          /**
           * baz description
           *
           * @return {(string|Array)} return description
           */
          Bar.prototype.baz=function () {
           // TODO
          };
          

          2.4.13 細(xì)節(jié)注釋

          對(duì)于內(nèi)部實(shí)現(xiàn)、不容易理解的邏輯說(shuō)明、摘要信息等,我們可能需要編寫細(xì)節(jié)注釋。

          [建議] 細(xì)節(jié)注釋遵循單行注釋的格式。說(shuō)明必須換行時(shí),每行是一個(gè)單行注釋的起始。

          function foo(p1, p2, opt_p3) {
           // 這里對(duì)具體內(nèi)部邏輯進(jìn)行說(shuō)明
           // 說(shuō)明太長(zhǎng)需要換行
           for (...) {
           ....
           }
          }
          

          [強(qiáng)制] 有時(shí)我們會(huì)使用一些特殊標(biāo)記進(jìn)行說(shuō)明。特殊標(biāo)記必須使用單行注釋的形式。下面列舉了一些常用標(biāo)記:

          解釋:

          1. TODO: 有功能待實(shí)現(xiàn)。此時(shí)需要對(duì)將要實(shí)現(xiàn)的功能進(jìn)行簡(jiǎn)單說(shuō)明。
          2. FIXME: 該處代碼運(yùn)行沒(méi)問(wèn)題,但可能由于時(shí)間趕或者其他原因,需要修正。此時(shí)需要對(duì)如何修正進(jìn)行簡(jiǎn)單說(shuō)明。
          3. HACK: 為修正某些問(wèn)題而寫的不太好或者使用了某些詭異手段的代碼。此時(shí)需要對(duì)思路或詭異手段進(jìn)行描述。
          4. XXX: 該處存在陷阱。此時(shí)需要對(duì)陷阱進(jìn)行描述。

          3 語(yǔ)言特性

          3.1 變量

          [強(qiáng)制] 變量在使用前必須通過(guò) var 定義。

          解釋:不通過(guò) var 定義變量將導(dǎo)致變量污染全局環(huán)境。

          // good
          var name='MyName';
          // bad
          name='MyName';
          

          [強(qiáng)制] 每個(gè) var 只能聲明一個(gè)變量。

          解釋:一個(gè) var 聲明多個(gè)變量,容易導(dǎo)致較長(zhǎng)的行長(zhǎng)度,并且在修改時(shí)容易造成逗號(hào)和分號(hào)的混淆。

          // good
          var hangModules=[];
          var missModules=[];
          var visited={};
          // bad
          var hangModules=[],
           missModules=[],
           visited={};
          

          [強(qiáng)制] 變量必須 即用即聲明,不得在函數(shù)或其它形式的代碼塊起始位置統(tǒng)一聲明所有變量。

          解釋: 變量聲明與使用的距離越遠(yuǎn),出現(xiàn)的跨度越大,代碼的閱讀與維護(hù)成本越高。雖然JavaScript的變量是函數(shù)作用域,還是應(yīng)該根據(jù)編程中的意圖,縮小變量出現(xiàn)的距離空間。

          // good
          function kv2List(source) {
           var list=[];
           for (var key in source) {
           if (source.hasOwnProperty(key)) {
           var item={
           k: key,
           v: source[key]
           };
           list.push(item);
           }
           }
           return list;
          }
          // bad
          function kv2List(source) {
           var list=[];
           var key;
           var item;
           for (key in source) {
           if (source.hasOwnProperty(key)) {
           item={
           k: key,
           v: source[key]
           };
           list.push(item);
           }
           }
           return list;
          }
          

          3.2 條件

          [強(qiáng)制] 在 Equality Expression 中使用類型嚴(yán)格的===。僅當(dāng)判斷 null 或 undefined 時(shí),允許使用==null。

          解釋:使用===可以避免等于判斷中隱式的類型轉(zhuǎn)換。

          // good
          if (age===30) {
           // ......
          }
          // bad
          if (age==30) {
           // ......
          }
          

          [建議] 盡可能使用簡(jiǎn)潔的表達(dá)式。

          // 字符串為空
          // good
          if (!name) {
           // ......
          }
          // bad
          if (name==='') {
           // ......
          }
          // 字符串非空
          // good
          if (name) {
           // ......
          }
          // bad
          if (name !=='') {
           // ......
          }
          // 數(shù)組非空
          // good
          if (collection.length) {
           // ......
          }
          // bad
          if (collection.length > 0) {
           // ......
          }
          // 布爾不成立
          // good
          if (!notTrue) {
           // ......
          }
          // bad
          if (notTrue===false) {
           // ......
          }
          // null 或 undefined
          // good
          if (noValue==null) {
           // ......
          }
          // bad
          if (noValue===null || typeof noValue==='undefined') {
           // ......
          }
          

          [建議] 按執(zhí)行頻率排列分支的順序。

          解釋:按執(zhí)行頻率排列分支的順序好處是:

          1. 閱讀的人容易找到最常見(jiàn)的情況,增加可讀性。
          2. 提高執(zhí)行效率。

          [建議] 對(duì)于相同變量或表達(dá)式的多值條件,用 switch 代替 if。

          // good
          switch (typeof variable) {
           case 'object':
           // ......
           break;
           case 'number':
           case 'boolean':
           case 'string':
           // ......
           break;
          }
          // bad
          var type=typeof variable;
          if (type==='object') {
           // ......
          } 
          else if (type==='number' || type==='boolean' || type==='string') {
           // ......
          }
          

          [建議] 如果函數(shù)或全局中的 else 塊后沒(méi)有任何語(yǔ)句,可以刪除 else。

          示例:

          // good
          function getName() {
           if (name) {
           return name;
           }
           return 'unnamed';
          }
          // bad
          function getName() {
           if (name) {
           return name;
           }
           else {
           return 'unnamed';
           }
          }
          

          3.3 循環(huán)

          [建議] 不要在循環(huán)體中包含函數(shù)表達(dá)式,事先將函數(shù)提取到循環(huán)體外。

          解釋:循環(huán)體中的函數(shù)表達(dá)式,運(yùn)行過(guò)程中會(huì)生成循環(huán)次數(shù)個(gè)函數(shù)對(duì)象。

          // good
          function clicker() {
           // ......
          }
          for (var i=0, len=elements.length; i < len; i++) {
           var element=elements[i];
           addListener(element, 'click', clicker);
          }
          // bad
          for (var i=0, len=elements.length; i < len; i++) {
           var element=elements[i];
           addListener(element, 'click', function () {});
          }
          

          [建議] 對(duì)循環(huán)內(nèi)多次使用的不變值,在循環(huán)外用變量緩存。

          // good
          var width=wrap.offsetWidth + 'px';
          for (var i=0, len=elements.length; i < len; i++) {
           var element=elements[i];
           element.style.width=width;
           // ......
          }
          // bad
          for (var i=0, len=elements.length; i < len; i++) {
           var element=elements[i];
           element.style.width=wrap.offsetWidth + 'px';
           // ......
          }
          

          [建議] 對(duì)有序集合進(jìn)行遍歷時(shí),緩存 length。

          解釋:雖然現(xiàn)代瀏覽器都對(duì)數(shù)組長(zhǎng)度進(jìn)行了緩存,但對(duì)于一些宿主對(duì)象和老舊瀏覽器的數(shù)組對(duì)象,在每次 length 訪問(wèn)時(shí)會(huì)動(dòng)態(tài)計(jì)算元素個(gè)數(shù),此時(shí)緩存 length 能有效提高程序性能。

          for (var i=0, len=elements.length; i < len; i++) {
           var element=elements[i];
           // ......
          }
          

          [建議] 對(duì)有序集合進(jìn)行順序無(wú)關(guān)的遍歷時(shí),使用逆序遍歷。

          解釋:逆序遍歷可以節(jié)省變量,代碼比較優(yōu)化。

          var len=elements.length;
          while (len--) {
           var element=elements[len];
           // ......
          }
          

          3.4 類型

          3.4.1 類型檢測(cè)

          [建議] 類型檢測(cè)優(yōu)先使用 typeof。對(duì)象類型檢測(cè)使用 instanceof。null 或 undefined 的檢測(cè)使用==null。

          // string
          typeof variable==='string'
          // number
          typeof variable==='number'
          // boolean
          typeof variable==='boolean'
          // Function
          typeof variable==='function'
          // Object
          typeof variable==='object'
          // RegExp
          variable instanceof RegExp
          // Array
          variable instanceof Array
          // null
          variable===null
          // null or undefined
          variable==null
          // undefined
          typeof variable==='undefined'
          

          3.4.2 類型轉(zhuǎn)換

          [建議] 轉(zhuǎn)換成 string 時(shí),使用 + ''。

          // good
          num + '';
          // bad
          new String(num);
          num.toString();
          String(num);
          

          [建議] 轉(zhuǎn)換成 number 時(shí),通常使用 +。

          // good
          +str;
          // bad
          Number(str);
          

          [建議] string 轉(zhuǎn)換成 number,要轉(zhuǎn)換的字符串結(jié)尾包含非數(shù)字并期望忽略時(shí),使用 parseInt。

          var width='200px';
          parseInt(width, 10);
          

          [強(qiáng)制] 使用 parseInt 時(shí),必須指定進(jìn)制。

          // good
          parseInt(str, 10);
          // bad
          parseInt(str);
          

          [建議] 轉(zhuǎn)換成 boolean 時(shí),使用 !!。

          var num=3.14;
          !!num;
          

          [建議] number 去除小數(shù)點(diǎn),使用 Math.floor / Math.round / Math.ceil,不使用 parseInt。

          // good
          var num=3.14;
          Math.ceil(num);
          // bad
          var num=3.14;
          parseInt(num, 10);
          

          3.5 字符串

          [強(qiáng)制] 字符串開(kāi)頭和結(jié)束使用單引號(hào) '。

          解釋:

          1. 輸入單引號(hào)不需要按住 shift,方便輸入。
          2. 實(shí)際使用中,字符串經(jīng)常用來(lái)拼接 HTML。為方便 HTML 中包含雙引號(hào)而不需要轉(zhuǎn)義寫法。
          var str='我是一個(gè)字符串';
          var html='<div class="cls">拼接HTML可以省去雙引號(hào)轉(zhuǎn)義</div>';
          

          [建議] 使用 數(shù)組 或 + 拼接字符串。

          解釋:

          1. 使用 + 拼接字符串,如果拼接的全部是 StringLiteral,壓縮工具可以對(duì)其進(jìn)行自動(dòng)合并的優(yōu)化。所以,靜態(tài)字符串建議使用 + 拼接。
          2. 在現(xiàn)代瀏覽器下,使用 + 拼接字符串,性能較數(shù)組的方式要高。
          3. 如需要兼顧老舊瀏覽器,應(yīng)盡量使用數(shù)組拼接字符串。

          示例:

          // 使用數(shù)組拼接字符串
          var str=[
           // 推薦換行開(kāi)始并縮進(jìn)開(kāi)始第一個(gè)字符串, 對(duì)齊代碼, 方便閱讀.
           '<ul>',
           '<li>第一項(xiàng)</li>',
           '<li>第二項(xiàng)</li>',
           '</ul>'
          ].join('');
          // 使用 + 拼接字符串
          var str2='' // 建議第一個(gè)為空字符串, 第二個(gè)換行開(kāi)始并縮進(jìn)開(kāi)始, 對(duì)齊代碼, 方便閱讀
           + '<ul>',
           + '<li>第一項(xiàng)</li>',
           + '<li>第二項(xiàng)</li>',
           + '</ul>';
          

          [建議] 復(fù)雜的數(shù)據(jù)到視圖字符串的轉(zhuǎn)換過(guò)程,選用一種模板引擎。

          解釋:使用模板引擎有如下好處:

          1. 在開(kāi)發(fā)過(guò)程中專注于數(shù)據(jù),將視圖生成的過(guò)程由另外一個(gè)層級(jí)維護(hù),使程序邏輯結(jié)構(gòu)更清晰。
          2. 優(yōu)秀的模板引擎,通過(guò)模板編譯技術(shù)和高質(zhì)量的編譯產(chǎn)物,能獲得比手工拼接字符串更高的性能。
          • artTemplate: 體積較小,在所有環(huán)境下性能高,語(yǔ)法靈活。
          • dot.js: 體積小,在現(xiàn)代瀏覽器下性能高,語(yǔ)法靈活。
          • etpl: 體積較小,在所有環(huán)境下性能高,模板復(fù)用性高,語(yǔ)法靈活。
          • handlebars: 體積大,在所有環(huán)境下性能高,擴(kuò)展性高。
          • hogon: 體積小,在現(xiàn)代瀏覽器下性能高。
          • nunjucks: 體積較大,性能一般,模板復(fù)用性高。

          3.6 對(duì)象

          [強(qiáng)制] 使用對(duì)象字面量 {} 創(chuàng)建新 Object。

          // good
          var obj={};
          // bad
          var obj=new Object();
          

          [強(qiáng)制] 對(duì)象創(chuàng)建時(shí),如果一個(gè)對(duì)象的所有 屬性 均可以不添加引號(hào),則所有 屬性 不得添加引號(hào)。

          var info={
           name: 'someone',
           age: 28
          };
          

          [強(qiáng)制] 對(duì)象創(chuàng)建時(shí),如果任何一個(gè) 屬性 需要添加引號(hào),則所有 屬性 必須添加 '。

          解釋:如果屬性不符合 Identifier 和 NumberLiteral 的形式,就需要以 StringLiteral 的形式提供。

          // good
          var info={
           'name': 'someone',
           'age': 28,
           'more-info': '...'
          };
          // bad
          var info={
           name: 'someone',
           age: 28,
           'more-info': '...'
          };
          

          [強(qiáng)制] 不允許修改和擴(kuò)展任何原生對(duì)象和宿主對(duì)象的原型。

          // 以下行為絕對(duì)禁止
          String.prototype.trim=function () {
          };
          

          [建議] 屬性訪問(wèn)時(shí),盡量使用 .。

          解釋:屬性名符合 Identifier 的要求,就可以通過(guò) . 來(lái)訪問(wèn),否則就只能通過(guò) [expr] 方式訪問(wèn)。

          通常在 JavaScript 中聲明的對(duì)象,屬性命名是使用 Camel 命名法,用 . 來(lái)訪問(wèn)更清晰簡(jiǎn)潔。部分特殊的屬性(比如來(lái)自后端的JSON),可能采用不尋常的命名方式,可以通過(guò) [expr] 方式訪問(wèn)。

          info.age;
          info['more-info'];
          

          [建議] for in 遍歷對(duì)象時(shí), 使用 hasOwnProperty 過(guò)濾掉原型中的屬性。

          var newInfo={};
          for (var key in info) {
           if (info.hasOwnProperty(key)) {
           newInfo[key]=info[key];
           }
          }
          

          3.7 數(shù)組

          [強(qiáng)制] 使用數(shù)組字面量 [] 創(chuàng)建新數(shù)組,除非想要?jiǎng)?chuàng)建的是指定長(zhǎng)度的數(shù)組。

          // good
          var arr=[];
          // bad
          var arr=new Array();
          

          [強(qiáng)制] 遍歷數(shù)組不使用 for in。

          解釋:數(shù)組對(duì)象可能存在數(shù)字以外的屬性, 這種情況下 for in 不會(huì)得到正確結(jié)果.

          var arr=['a', 'b', 'c'];
          arr.other='other things'; // 這里僅作演示, 實(shí)際中應(yīng)使用Object類型
          // 正確的遍歷方式
          for (var i=0, len=arr.length; i < len; i++) {
           console.log(i);
          }
          // 錯(cuò)誤的遍歷方式
          for (i in arr) {
           console.log(i);
          }
          

          [建議] 不因?yàn)樾阅艿脑蜃约簩?shí)現(xiàn)數(shù)組排序功能,盡量使用數(shù)組的 sort 方法。

          解釋:自己實(shí)現(xiàn)的常規(guī)排序算法,在性能上并不優(yōu)于數(shù)組默認(rèn)的 sort 方法。以下兩種場(chǎng)景可以自己實(shí)現(xiàn)排序:

          1. 需要穩(wěn)定的排序算法,達(dá)到嚴(yán)格一致的排序結(jié)果。
          2. 數(shù)據(jù)特點(diǎn)鮮明,適合使用桶排。

          [建議] 清空數(shù)組使用 .length=0。

          3.8 函數(shù)

          3.8.1 函數(shù)長(zhǎng)度

          [建議] 一個(gè)函數(shù)的長(zhǎng)度控制在 50 行以內(nèi)。

          解釋:將過(guò)多的邏輯單元混在一個(gè)大函數(shù)中,易導(dǎo)致難以維護(hù)。一個(gè)清晰易懂的函數(shù)應(yīng)該完成單一的邏輯單元。復(fù)雜的操作應(yīng)進(jìn)一步抽取,通過(guò)函數(shù)的調(diào)用來(lái)體現(xiàn)流程。

          特定算法等不可分割的邏輯允許例外。

          function syncViewStateOnUserAction() {
           if (x.checked) {
           y.checked=true;
           z.value='';
           }
           else {
           y.checked=false;
           }
           if (!a.value) {
           warning.innerText='Please enter it';
           submitButton.disabled=true;
           }
           else {
           warning.innerText='';
           submitButton.disabled=false;
           }
          }
          // 直接閱讀該函數(shù)會(huì)難以明確其主線邏輯,因此下方是一種更合理的表達(dá)方式:
          function syncViewStateOnUserAction() {
           syncXStateToView();
           checkAAvailability();
          }
          function syncXStateToView() {
           if (x.checked) {
           y.checked=true;
           z.value='';
           }
           else {
           y.checked=false;
           }
          }
          function checkAAvailability() {
           if (!a.value) {
           displayWarningForAMissing();
           }
           else {
           clearWarnignForA();
           }
          }
          

          3.8.2 參數(shù)設(shè)計(jì)

          [建議] 一個(gè)函數(shù)的參數(shù)控制在 6 個(gè)以內(nèi)。

          解釋:

          除去不定長(zhǎng)參數(shù)以外,函數(shù)具備不同邏輯意義的參數(shù)建議控制在 6 個(gè)以內(nèi),過(guò)多參數(shù)會(huì)導(dǎo)致維護(hù)難度增大。

          某些情況下,如使用 AMD Loader 的 require 加載多個(gè)模塊時(shí),其 callback 可能會(huì)存在較多參數(shù),因此對(duì)函數(shù)參數(shù)的個(gè)數(shù)不做強(qiáng)制限制。

          [建議] 通過(guò) options 參數(shù)傳遞非數(shù)據(jù)輸入型參數(shù)。

          解釋:有些函數(shù)的參數(shù)并不是作為算法的輸入,而是對(duì)算法的某些分支條件判斷之用,此類參數(shù)建議通過(guò)一個(gè) options 參數(shù)傳遞。

          如下函數(shù):

          /**
           * 移除某個(gè)元素
           *
           * @param {Node} element 需要移除的元素
           * @param {boolean} removeEventListeners 是否同時(shí)將所有注冊(cè)在元素上的事件移除
           */
          function removeElement(element, removeEventListeners) {
           element.parent.removeChild(element);
           if (removeEventListeners) {
           element.clearEventListeners();
           }
          }
          

          可以轉(zhuǎn)換為下面的簽名:

          /**
           * 移除某個(gè)元素
           *
           * @param {Node} element 需要移除的元素
           * @param {Object} options 相關(guān)的邏輯配置
           * @param {boolean} options.removeEventListeners 是否同時(shí)將所有注冊(cè)在元素上的事件移除
           */
          function removeElement(element, options) {
           element.parent.removeChild(element);
           if (options.removeEventListeners) {
           element.clearEventListeners();
           }
          }
          

          這種模式有幾個(gè)顯著的優(yōu)勢(shì):

          • boolean 型的配置項(xiàng)具備名稱,從調(diào)用的代碼上更易理解其表達(dá)的邏輯意義。
          • 當(dāng)配置項(xiàng)有增長(zhǎng)時(shí),無(wú)需無(wú)休止地增加參數(shù)個(gè)數(shù),不會(huì)出現(xiàn) removeElement(element, true, false, false, 3) 這樣難以理解的調(diào)用代碼。
          • 當(dāng)部分配置參數(shù)可選時(shí),多個(gè)參數(shù)的形式非常難處理重載邏輯,而使用一個(gè) options 對(duì)象只需判斷屬性是否存在,實(shí)現(xiàn)得以簡(jiǎn)化。

          3.8.3 閉包

          [建議] 在適當(dāng)?shù)臅r(shí)候?qū)㈤]包內(nèi)大對(duì)象置為 null。

          解釋:

          在 JavaScript 中,無(wú)需特別的關(guān)鍵詞就可以使用閉包,一個(gè)函數(shù)可以任意訪問(wèn)在其定義的作用域外的變量。需要注意的是,函數(shù)的作用域是靜態(tài)的,即在定義時(shí)決定,與調(diào)用的時(shí)機(jī)和方式?jīng)]有任何關(guān)系。

          閉包會(huì)阻止一些變量的垃圾回收,對(duì)于較老舊的JavaScript引擎,可能導(dǎo)致外部所有變量均無(wú)法回收。

          首先一個(gè)較為明確的結(jié)論是,以下內(nèi)容會(huì)影響到閉包內(nèi)變量的回收:

          • 嵌套的函數(shù)中是否有使用該變量。
          • 嵌套的函數(shù)中是否有 直接調(diào)用eval
          • 是否使用了 with 表達(dá)式。

          Chakra、V8 和 SpiderMonkey 將受以上因素的影響,表現(xiàn)出不盡相同又較為相似的回收策略,而JScript.dll和Carakan則完全沒(méi)有這方面的優(yōu)化,會(huì)完整保留整個(gè) LexicalEnvironment 中的所有變量綁定,造成一定的內(nèi)存消耗。

          由于對(duì)閉包內(nèi)變量有回收優(yōu)化策略的 Chakra、V8 和 SpiderMonkey 引擎的行為較為相似,因此可以總結(jié)如下,當(dāng)返回一個(gè)函數(shù) fn 時(shí):

          1. 如果 fn 的 [[Scope]] 是ObjectEnvironment(with 表達(dá)式生成 ObjectEnvironment,函數(shù)和 catch 表達(dá)式生成 DeclarativeEnvironment),則:
          2. 如果是 V8 引擎,則退出全過(guò)程。
          3. 如果是 SpiderMonkey,則處理該 ObjectEnvironment 的外層 LexicalEnvironment。
          4. 獲取當(dāng)前 LexicalEnvironment 下的所有類型為 Function 的對(duì)象,對(duì)于每一個(gè) Function 對(duì)象,分析其 FunctionBody:
          5. 如果 FunctionBody 中含有 直接調(diào)用eval,則退出全過(guò)程。
          6. 否則得到所有的 Identifier。
          7. 對(duì)于每一個(gè) Identifier,設(shè)其為 name,根據(jù)查找變量引用的規(guī)則,從 LexicalEnvironment 中找出名稱為 name 的綁定 binding。
          8. 對(duì) binding 添加 notSwap 屬性,其值為 true。
          9. 檢查當(dāng)前 LexicalEnvironment 中的每一個(gè)變量綁定,如果該綁定有 notSwap 屬性且值為 true,則:
          10. 如果是V8引擎,刪除該綁定。
          11. 如果是SpiderMonkey,將該綁定的值設(shè)為 undefined,將刪除 notSwap 屬性。

          對(duì)于Chakra引擎,暫無(wú)法得知是按 V8 的模式還是按 SpiderMonkey 的模式進(jìn)行。

          如果有 非常龐大 的對(duì)象,且預(yù)計(jì)會(huì)在 老舊的引擎 中執(zhí)行,則使用閉包時(shí),注意將閉包不需要的對(duì)象置為空引用。

          [建議] 使用 IIFE 避免 Lift 效應(yīng)。

          解釋:在引用函數(shù)外部變量時(shí),函數(shù)執(zhí)行時(shí)外部變量的值由運(yùn)行時(shí)決定而非定義時(shí),最典型的場(chǎng)景如下:

          var tasks=[];
          for (var i=0; i < 5; i++) {
           tasks[tasks.length]=function () {
           console.log('Current cursor is at ' + i);
           };
          }
          var len=tasks.length;
          while (len--) {
           tasks[len]();
          }
          

          以上代碼對(duì) tasks 中的函數(shù)的執(zhí)行均會(huì)輸出 Current cursor is at 5,往往不符合預(yù)期。

          此現(xiàn)象稱為 Lift 效應(yīng) 。解決的方式是通過(guò)額外加上一層閉包函數(shù),將需要的外部變量作為參數(shù)傳遞來(lái)解除變量的綁定關(guān)系:

          var tasks=[];
          for (var i=0; i < 5; i++) {
           // 注意有一層額外的閉包
           tasks[tasks.length]=(function (i) {
           return function () {
           console.log('Current cursor is at ' + i);
           };
           })(i);
          }
          var len=tasks.length;
          while (len--) {
           tasks[len]();
          }
          

          3.8.4 空函數(shù)

          [建議] 空函數(shù)不使用 new Function() 的形式。

          var emptyFunction=function () {};
          

          [建議] 對(duì)于性能有高要求的場(chǎng)合,建議存在一個(gè)空函數(shù)的常量,供多處使用共享。

          var EMPTY_FUNCTION=function () {};
          function MyClass() {
          }
          MyClass.prototype.abstractMethod=EMPTY_FUNCTION;
          MyClass.prototype.hooks.before=EMPTY_FUNCTION;
          MyClass.prototype.hooks.after=EMPTY_FUNCTION;
          

          3.9 面向?qū)ο?/p>

          [強(qiáng)制] 類的繼承方案,實(shí)現(xiàn)時(shí)需要修正 constructor。

          解釋:通常使用其他 library 的類繼承方案都會(huì)進(jìn)行 constructor 修正。如果是自己實(shí)現(xiàn)的類繼承方案,需要進(jìn)行 constructor 修正。

          /**
           * 構(gòu)建類之間的繼承關(guān)系
           * 
           * @param {Function} subClass 子類函數(shù)
           * @param {Function} superClass 父類函數(shù)
           */
          function inherits(subClass, superClass) {
           var F=new Function();
           F.prototype=superClass.prototype;
           subClass.prototype=new F();
           subClass.prototype.constructor=subClass;
          }
          

          [建議] 聲明類時(shí),保證 constructor 的正確性。

          function Animal(name) {
           this.name=name;
          }
          // 直接prototype等于對(duì)象時(shí),需要修正constructor
          Animal.prototype={
           constructor: Animal,
           jump: function () {
           alert('animal ' + this.name + ' jump');
           }
          };
          // 這種方式擴(kuò)展prototype則無(wú)需理會(huì)constructor
          Animal.prototype.jump=function () {
           alert('animal ' + this.name + ' jump');
          };
          

          [建議] 屬性在構(gòu)造函數(shù)中聲明,方法在原型中聲明。

          解釋: 原型對(duì)象的成員被所有實(shí)例共享,能節(jié)約內(nèi)存占用。所以編碼時(shí)我們應(yīng)該遵守這樣的原則:原型對(duì)象包含程序不會(huì)修改的成員,如方法函數(shù)或配置項(xiàng)。

          function TextNode(value, engine) {
           this.value=value;
           this.engine=engine;
          }
          TextNode.prototype.clone=function () {
           return this;
          };
          

          [強(qiáng)制] 自定義事件的 事件名 必須全小寫。

          解釋:在 JavaScript 廣泛應(yīng)用的瀏覽器環(huán)境,絕大多數(shù) DOM 事件名稱都是全小寫的。為了遵循大多數(shù) JavaScript 開(kāi)發(fā)者的習(xí)慣,在設(shè)計(jì)自定義事件時(shí),事件名也應(yīng)該全小寫。

          [強(qiáng)制] 自定義事件只能有一個(gè) event 參數(shù)。如果事件需要傳遞較多信息,應(yīng)仔細(xì)設(shè)計(jì)事件對(duì)象。

          解釋:一個(gè)事件對(duì)象的好處有:

          1. 順序無(wú)關(guān),避免事件監(jiān)聽(tīng)者需要記憶參數(shù)順序。
          2. 每個(gè)事件信息都可以根據(jù)需要提供或者不提供,更自由。
          3. 擴(kuò)展方便,未來(lái)添加事件信息時(shí),無(wú)需考慮會(huì)破壞監(jiān)聽(tīng)器參數(shù)形式而無(wú)法向后兼容。

          [建議] 設(shè)計(jì)自定義事件時(shí),應(yīng)考慮禁止默認(rèn)行為。

          解釋:常見(jiàn)禁止默認(rèn)行為的方式有兩種:

          1. 事件監(jiān)聽(tīng)函數(shù)中 return false。
          2. 事件對(duì)象中包含禁止默認(rèn)行為的方法,如 preventDefault。

          3.10 動(dòng)態(tài)特性

          3.10.1 eval

          [強(qiáng)制] 避免使用直接 eval 函數(shù)。

          解釋:直接 eval,指的是以函數(shù)方式調(diào)用 eval 的調(diào)用方法。直接 eval 調(diào)用執(zhí)行代碼的作用域?yàn)楸镜刈饔糜颍瑧?yīng)當(dāng)避免。

          如果有特殊情況需要使用直接 eval,需在代碼中用詳細(xì)的注釋說(shuō)明為何必須使用直接 eval,不能使用其它動(dòng)態(tài)執(zhí)行代碼的方式,同時(shí)需要其他資深工程師進(jìn)行 Code Review。

          [建議] 盡量避免使用 eval 函數(shù)。

          3.10.2 動(dòng)態(tài)執(zhí)行代碼

          [建議] 使用 new Function 執(zhí)行動(dòng)態(tài)代碼。

          解釋:通過(guò) new Function 生成的函數(shù)作用域是全局使用域,不會(huì)影響當(dāng)當(dāng)前的本地作用域。如果有動(dòng)態(tài)代碼執(zhí)行的需求,建議使用 new Function。

          var handler=new Function('x', 'y', 'return x + y;');
          var result=handler($('#x').val(), $('#y').val());
          

          3.10.3 with

          [建議] 盡量不要使用 with。

          解釋:使用 with 可能會(huì)增加代碼的復(fù)雜度,不利于閱讀和管理;也會(huì)對(duì)性能有影響。大多數(shù)使用 with 的場(chǎng)景都能使用其他方式較好的替代。所以,盡量不要使用 with。

          3.10.4 delete

          [建議] 減少 delete 的使用。

          解釋:如果沒(méi)有特別的需求,減少或避免使用delete。delete的使用會(huì)破壞部分 JavaScript 引擎的性能優(yōu)化。

          [建議] 處理 delete 可能產(chǎn)生的異常。

          解釋:

          對(duì)于有被遍歷需求,且值 null 被認(rèn)為具有業(yè)務(wù)邏輯意義的值的對(duì)象,移除某個(gè)屬性必須使用 delete 操作。

          在嚴(yán)格模式或IE下使用 delete 時(shí),不能被刪除的屬性會(huì)拋出異常,因此在不確定屬性是否可以刪除的情況下,建議添加 try-catch 塊。

          try {
           delete o.x;
          }
          catch (deleteError) {
           o.x=null;
          }
          

          3.10.5 對(duì)象屬性

          [建議] 避免修改外部傳入的對(duì)象。

          解釋:

          JavaScript 因其腳本語(yǔ)言的動(dòng)態(tài)特性,當(dāng)一個(gè)對(duì)象未被 seal 或 freeze 時(shí),可以任意添加、刪除、修改屬性值。

          但是隨意地對(duì) 非自身控制的對(duì)象 進(jìn)行修改,很容易造成代碼在不可預(yù)知的情況下出現(xiàn)問(wèn)題。因此,設(shè)計(jì)良好的組件、函數(shù)應(yīng)該避免對(duì)外部傳入的對(duì)象的修改。

          下面代碼的 selectNode 方法修改了由外部傳入的 datasource 對(duì)象。如果 datasource 用在其它場(chǎng)合(如另一個(gè) Tree 實(shí)例)下,會(huì)造成狀態(tài)的混亂。

          function Tree(datasource) {
           this.datasource=datasource;
          }
          Tree.prototype.selectNode=function (id) {
           // 從datasource中找出節(jié)點(diǎn)對(duì)象
           var node=this.findNode(id);
           if (node) {
           node.selected=true;
           this.flushView();
           }
          };
          

          對(duì)于此類場(chǎng)景,需要使用額外的對(duì)象來(lái)維護(hù),使用由自身控制,不與外部產(chǎn)生任何交互的 selectedNodeIndex 對(duì)象來(lái)維護(hù)節(jié)點(diǎn)的選中狀態(tài),不對(duì) datasource 作任何修改。

          function Tree(datasource) {
           this.datasource=datasource;
           this.selectedNodeIndex={};
          }
          Tree.prototype.selectNode=function (id) {
           // 從datasource中找出節(jié)點(diǎn)對(duì)象
           var node=this.findNode(id);
           if (node) {
           this.selectedNodeIndex[id]=true;
           this.flushView();
           }
          };
          

          除此之外,也可以通過(guò) deepClone 等手段將自身維護(hù)的對(duì)象與外部傳入的分離,保證不會(huì)相互影響。

          [建議] 具備強(qiáng)類型的設(shè)計(jì)。

          解釋:

          • 如果一個(gè)屬性被設(shè)計(jì)為 boolean 類型,則不要使用 1 / 0 作為其值。對(duì)于標(biāo)識(shí)性的屬性,如對(duì)代碼體積有嚴(yán)格要求,可以從一開(kāi)始就設(shè)計(jì)為 number 類型且將 0 作為否定值。
          • 從 DOM 中取出的值通常為 string 類型,如果有對(duì)象或函數(shù)的接收類型為 number 類型,提前作好轉(zhuǎn)換,而不是期望對(duì)象、函數(shù)可以處理多類型的值。

          4 瀏覽器環(huán)境

          4.1 模塊化

          4.1.1 AMD

          [強(qiáng)制] 使用 AMD 作為模塊定義。

          解釋:

          AMD 作為由社區(qū)認(rèn)可的模塊定義形式,提供多種重載提供靈活的使用方式,并且絕大多數(shù)優(yōu)秀的 Library 都支持 AMD,適合作為規(guī)范。

          目前,比較成熟的 AMD Loader 有:

          • 官方實(shí)現(xiàn)的 requirejs
          • 百度自己實(shí)現(xiàn)的 esl

          [強(qiáng)制] 模塊 id 必須符合標(biāo)準(zhǔn)。

          解釋:模塊 id 必須符合以下約束條件:

          1. 類型為 string,并且是由 / 分割的一系列 terms 來(lái)組成。例如:this/is/a/module。
          2. term 應(yīng)該符合 [a-zA-Z0-9_-]+ 規(guī)則。
          3. 不應(yīng)該有 .js 后綴。
          4. 跟文件的路徑保持一致。

          4.1.2 define

          [建議] 定義模塊時(shí)不要指明 id 和 dependencies。

          解釋:

          在 AMD 的設(shè)計(jì)思想里,模塊名稱是和所在路徑相關(guān)的,匿名的模塊更利于封包和遷移。模塊依賴應(yīng)在模塊定義內(nèi)部通過(guò) local require 引用。

          所以,推薦使用 define(factory) 的形式進(jìn)行模塊定義。

          define(
           function (require) {
           }
          );
          

          [建議] 使用 return 來(lái)返回模塊定義。

          解釋:使用 return 可以減少 factory 接收的參數(shù)(不需要接收 exports 和 module),在沒(méi)有 AMD Loader 的場(chǎng)景下也更容易進(jìn)行簡(jiǎn)單的處理來(lái)偽造一個(gè) Loader。

          define(
           function (require) {
           var exports={};
           // ...
           return exports;
           }
          );
          

          4.1.3 require

          [強(qiáng)制] 全局運(yùn)行環(huán)境中,require 必須以 async require 形式調(diào)用。

          解釋:模塊的加載過(guò)程是異步的,同步調(diào)用并無(wú)法保證得到正確的結(jié)果。

          // good
          require(['foo'], function (foo) {
          });
          // bad
          var foo=require('foo');
          

          [強(qiáng)制] 模塊定義中只允許使用 local require,不允許使用 global require。

          解釋:

          1. 在模塊定義中使用 global require,對(duì)封裝性是一種破壞。
          2. 在 AMD 里,global require 是可以被重命名的。并且 Loader 甚至沒(méi)有全局的 require 變量,而是用 Loader 名稱做為 global require。模塊定義不應(yīng)該依賴使用的 Loader。

          [強(qiáng)制] Package在實(shí)現(xiàn)時(shí),內(nèi)部模塊的 require 必須使用 relative id。

          解釋:對(duì)于任何可能通過(guò) 發(fā)布-引入 的形式復(fù)用的第三方庫(kù)、框架、包,開(kāi)發(fā)者所定義的名稱不代表使用者使用的名稱。因此不要基于任何名稱的假設(shè)。在實(shí)現(xiàn)源碼中,require 自身的其它模塊時(shí)使用 relative id。

          define(
           function (require) {
           var util=require('./util');
           }
          );
          

          [建議] 不會(huì)被調(diào)用的依賴模塊,在 factory 開(kāi)始處統(tǒng)一 require。

          解釋:有些模塊是依賴的模塊,但不會(huì)在模塊實(shí)現(xiàn)中被直接調(diào)用,最為典型的是 css / js / tpl 等 Plugin 所引入的外部?jī)?nèi)容。此類內(nèi)容建議放在模塊定義最開(kāi)始處統(tǒng)一引用。

          define(
           function (require) {
           require('css!foo.css');
           require('tpl!bar.tpl.html');
           // ...
           }
          );
          

          4.2 DOM

          4.2.1 元素獲取

          [建議] 對(duì)于單個(gè)元素,盡可能使用 document.getElementById 獲取,避免使用document.all。

          [建議] 對(duì)于多個(gè)元素的集合,盡可能使用 context.getElementsByTagName 獲取。其中 context 可以為 document 或其他元素。指定 tagName 參數(shù)為 * 可以獲得所有子元素。

          [建議] 遍歷元素集合時(shí),盡量緩存集合長(zhǎng)度。如需多次操作同一集合,則應(yīng)將集合轉(zhuǎn)為數(shù)組。

          解釋:原生獲取元素集合的結(jié)果并不直接引用 DOM 元素,而是對(duì)索引進(jìn)行讀取,所以 DOM 結(jié)構(gòu)的改變會(huì)實(shí)時(shí)反映到結(jié)果中。

          <div></div>
          <span></span>
          <script>
          var elements=document.getElementsByTagName('*');
          // 顯示為 DIV
          alert(elements[0].tagName);
          var div=elements[0];
          var p=document.createElement('p');
          document.body.insertBefore(p, div);
          // 顯示為 P
          alert(elements[0].tagName);
          </script>
          

          [建議] 獲取元素的直接子元素時(shí)使用 children。避免使用childNodes,除非預(yù)期是需要包含文本、注釋和屬性類型的節(jié)點(diǎn)。

          4.2.2 樣式獲取

          [建議] 獲取元素實(shí)際樣式信息時(shí),應(yīng)使用 getComputedStyle 或 currentStyle。

          解釋:通過(guò) style 只能獲得內(nèi)聯(lián)定義或通過(guò) JavaScript 直接設(shè)置的樣式。通過(guò) CSS class 設(shè)置的元素樣式無(wú)法直接通過(guò) style 獲取。

          4.2.3 樣式設(shè)置

          [建議] 盡可能通過(guò)為元素添加預(yù)定義的 className 來(lái)改變?cè)貥邮剑苊庵苯硬僮?style 設(shè)置。

          [強(qiáng)制] 通過(guò) style 對(duì)象設(shè)置元素樣式時(shí),對(duì)于帶單位非 0 值的屬性,不允許省略單位。

          解釋:除了 IE,標(biāo)準(zhǔn)瀏覽器會(huì)忽略不規(guī)范的屬性值,導(dǎo)致兼容性問(wèn)題。

          4.2.4 DOM 操作

          [建議] 操作 DOM 時(shí),盡量減少頁(yè)面 reflow。

          解釋:頁(yè)面 reflow 是非常耗時(shí)的行為,非常容易導(dǎo)致性能瓶頸。下面一些場(chǎng)景會(huì)觸發(fā)瀏覽器的reflow:

          • DOM元素的添加、修改(內(nèi)容)、刪除。
          • 應(yīng)用新的樣式或者修改任何影響元素布局的屬性。
          • Resize瀏覽器窗口、滾動(dòng)頁(yè)面。
          • 讀取元素的某些屬性(offsetLeft、offsetTop、offsetHeight、offsetWidth、scrollTop/Left/Width/Height、clientTop/Left/Width/Height、getComputedStyle()、currentStyle(in IE)) 。

          [建議] 盡量減少 DOM 操作。

          解釋:DOM 操作也是非常耗時(shí)的一種操作,減少 DOM 操作有助于提高性能。舉一個(gè)簡(jiǎn)單的例子,構(gòu)建一個(gè)列表。我們可以用兩種方式:

          1. 在循環(huán)體中 createElement 并 append 到父元素中。
          2. 在循環(huán)體中拼接 HTML 字符串,循環(huán)結(jié)束后寫父元素的 innerHTML。

          第一種方法看起來(lái)比較標(biāo)準(zhǔn),但是每次循環(huán)都會(huì)對(duì) DOM 進(jìn)行操作,性能極低。在這里推薦使用第二種方法。

          4.2.5 DOM 事件

          [建議] 優(yōu)先使用 addEventListener / attachEvent 綁定事件,避免直接在 HTML 屬性中或 DOM 的 expando 屬性綁定事件處理。

          解釋:expando 屬性綁定事件容易導(dǎo)致互相覆蓋。

          [建議] 使用 addEventListener 時(shí)第三個(gè)參數(shù)使用 false。

          解釋:標(biāo)準(zhǔn)瀏覽器中的 addEventListener 可以通過(guò)第三個(gè)參數(shù)指定兩種時(shí)間觸發(fā)模型:冒泡和捕獲。而 IE 的 attachEvent 僅支持冒泡的事件觸發(fā)。所以為了保持一致性,通常 addEventListener 的第三個(gè)參數(shù)都為 false。

          [建議] 在沒(méi)有事件自動(dòng)管理的框架支持下,應(yīng)持有監(jiān)聽(tīng)器函數(shù)的引用,在適當(dāng)時(shí)候(元素釋放、頁(yè)面卸載等)移除添加的監(jiān)聽(tīng)器。

          作者:前端切圖小弟,個(gè)人運(yùn)營(yíng)的公眾號(hào):前端讀者(fe_duzhe)


          主站蜘蛛池模板: 无码人妻一区二区三区免费n鬼沢| 久久4k岛国高清一区二区| 一区二区在线电影| 国产精品毛片一区二区三区| 精品成人一区二区三区四区| 国产不卡视频一区二区三区| 无码午夜人妻一区二区不卡视频| 亚洲美女一区二区三区| 立川理惠在线播放一区| 精品成人av一区二区三区| 国产伦精品一区二区三区在线观看 | 国产aⅴ一区二区三区| 一区二区在线播放视频| 国产成人精品一区二区三区免费 | 国产成人精品一区二区A片带套| 无码av免费毛片一区二区| 无码人妻久久一区二区三区蜜桃| 日韩A无码AV一区二区三区 | 欧美av色香蕉一区二区蜜桃小说| 日本福利一区二区| 精品一区二区三区在线观看视频| 中文字幕AV一区二区三区| 精品久久久久中文字幕一区| 无码人妻视频一区二区三区| 日韩精品午夜视频一区二区三区| 免费精品一区二区三区第35| 国产精品福利一区| 日韩AV片无码一区二区不卡| 久久精品动漫一区二区三区| 狠狠色婷婷久久一区二区| 一区二区精品视频| 一区二区三区美女视频| 日韩AV在线不卡一区二区三区 | 一区二区三区日韩精品| 久久se精品一区二区| 国产日韩一区二区三区在线观看| 精品国产一区二区三区久久狼| 无码人妻精品一区二区蜜桃网站 | 国产一区二区三区精品视频| 国产一区二区三区在线免费| 亚洲码欧美码一区二区三区|