整合營銷服務商

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

          免費咨詢熱線:

          js數(shù)據(jù)流之間的轉(zhuǎn)換blob、file、DataURL、canvas、ArrayBuffer

          blob轉(zhuǎn)成file

          利用new File();

          function blobToFile(blob, filename, type) {
          	return new File([blob], filename, { type })
          }
          
          blobToFile('test info', 'test', 'text/plain' )

          輸出如下

          更進一步了解可閱讀MDN File - Web API 鎺ュ彛鍙傝€? | MDNMDN Web DocsMDN logoMozilla logo

          Blob() - Web API 接口參考 | MDN講解

          將file轉(zhuǎn)換成DataURL

          利用URL.createObjectURL()

          <input type="file" id="file">
          <img id="img">
          let img = document.getElementById('img')
          let file = document.getElementById('file')
          file.onchange = function () {
          let imgFile = this.files[0]
          img.src = URL.createObjectURL(imgFile)
          	img.onload = function () {
          		URL.revokeObjectURL(this.src)
          	}
          }

          更進一步了解可閱讀 MDN URL.createObjectURL() - Web API 鎺ュ彛鍙傝€? | MDNMDN Web DocsMDN logoMozilla logo 講解

          利用FileReader.readAsDataURL()

          let img = document.getElementById('img')
          let file = document.getElementById('file')
          file.onchange = function (e) {
          let imgFile = this.files[0]
          let fileReader = new FileReader()
          fileReader.readAsDataURL(imgFile)
            fileReader.onload = function () {
            	img.src = this.result
            }
          }

          更進一步了解可閱讀FileReader - Web API 鎺ュ彛鍙傝€? | MDNMDN Web DocsMDN logoMozilla logo簡介

          將DataURL轉(zhuǎn)成file

          function dataURLToFile (dataUrl, fileName) {
          const dataArr = dataUrl.split(',')
          const mime = dataArr[0].match(/:(.*);/)[1]
          const originStr = atob(dataArr[1])
          	return new File([originStr], fileName, { type: mime })
          }
          dataURLToFile('data:text/plain;base64,YWFhYWFhYQ==', '測試文件')
          // File {name: '測試文件', lastModified: 1640784525620, lastModifiedDate: Wed Dec 29 2021 21:28:45 GMT+0800 (中國標準時間), webkitRelativePath: '', size: 7, …}

          復雜處理方式如下

          function dataURLToFile (dataUrl, filename) {
          const dataArr = dataUrl.split(',')
          const mime = dataArr[0].match(/:(.*);/)[1]
          const originStr = atob(dataArr[1])
          let n = originStr.length
          const u8Arr = new Uint8Array(n)
            while (n--) {
            	u8Arr[n] = originStr.charCodeAt(n)
            }
          return new File([u8Arr], filename, { type: mime })
          }
          dataURLToFile('data:text/plain;base64,YWFhYWFhYQ==', '測試文件')
          console.log(dataURLToFile('data:text/plain;base64,YWFhYWFhYQ==','測試文件'));
          // File {name: '測試文件', lastModified: 1640784866937, lastModifiedDate: Wed Dec 29 2021 21:34:26 GMT+0800 (中國標準時間), webkitRelativePath: '', size: 7, …}


          將canvas轉(zhuǎn)成DataURL

          利用canvas.toDataURL()

          // html
          <input type="file" accept="image/*" id="file">
          // js
          document.querySelector('#file').onchange = function () {
            canvasToDataURL(this.files[0]).then(res => console.log(res))
           }
          function canvasToDataURL (file) {
          return new Promise(resolve => {
              const img = document.createElement('img')
              img.src = URL.createObjectURL(file)
              img.onload = function () {
              const canvas = document.createElement('canvas')
              canvas.width = img.width
              canvas.height = img.height
              const ctx = canvas.getContext('2d')
              ctx.drawImage(img, 0, 0)
              resolve(canvas.toDataURL('image/png', 1))
              }
          })
          }


          將DataURL轉(zhuǎn)成canvas

          function dataUrlToCanvas (dataUrl) {
          return new Promise(resolve => {
              const img = new Image()
              img.src = dataUrl
              img.onload = function () {
              const canvas = document.createElement('canvas')
              canvas.width = this.width
              canvas.height = this.height
              const ctx = canvas.getContext('2d')
              ctx.drawImage(this, 0, 0)
              resolve(canvas)
              }
          })
          }
          const dataUrl = 'data:image/png;base64,iVBORw0KGgoAAAANSUh...'
          dataUrlToCanvas(dataUrl)
          .then(res => document.body.appendChild(res))


          將canvas轉(zhuǎn)成blob

          利用canvas.toBlob()


          // html
          <input type="file" accept="image/*" id="file">
          // js
          document.querySelector('#file').onchange = function () {
          canvasToDataURL(this.files[0])
          .then(res => console.log(res))
          }
          function canvasToDataURL (file) {
          return new Promise(resolve => {
            const img = document.createElement('img')
            img.src = URL.createObjectURL(file)
            img.onload = function () {
            const canvas = document.createElement('canvas')
            canvas.width = img.width
            canvas.height = img.height
            const ctx = canvas.getContext('2d')
            ctx.drawImage(img, 0, 0)
            canvas.toBlob(function (e) {
            	resolve(e)
            }, 'image/png', 1)
          }
          })


          將canvas轉(zhuǎn)成file

          將canvas轉(zhuǎn)成Blob,然后將Blob轉(zhuǎn)成file即可,可看最開始的文件類型轉(zhuǎn)換流程圖。

          或?qū)anvas轉(zhuǎn)成dataURL,然后將dataURL轉(zhuǎn)成file即可,可看最開始的文件類型轉(zhuǎn)換流程圖。

          blob轉(zhuǎn)arrayBuffer

          利用FileReader.readAsArrayBuffer()

          function blobToArrayBuffer (blob, callback) {
          const reader = new FileReader()
          reader.readAsArrayBuffer(blob)
          reader.onload = function () {
          	callback(this.result)
          }
          }
          let blob = new Blob([1, 2, 3, 4, 5])
          blobToArrayBuffer(blob, (arrayBuffer) => { console.log(arrayBuffer) })
          // ArrayBuffer(5)


          arrayBuffer轉(zhuǎn)blob

          利用new Blob()

          function arrayBufferToBlob (arrayBuffer, type) {
          	return new Blob([arrayBuffer], { type })
          }
          blobToArrayBuffer(new Blob([1, 2, 3, 4, 5]), (arrayBuffer) => {
          console.log(arrayBufferToBlob(arrayBuffer, 'text/plain'))
          // Blob {size: 5, type: 'text/plain'}
          })

          ————————————————

          版權(quán)聲明:本文為CSDN博主「定栓」的原創(chuàng)文章,遵循CC 4.0 BY-SA版權(quán)協(xié)議,轉(zhuǎn)載請附上原文出處鏈接及本聲明。

          原文鏈接:https://blog.csdn.net/weixin_44116302/article/details/122064841

          習大綱

          一、作用域【深入理解】

          1.概念

          作用域是指變量函數(shù)一個作用范圍,規(guī)定了變量和函數(shù)的在哪里去掉用,JS不會報錯

          ? 理解

          ? 強調(diào)思考

          num1 = ???; 222

          num2 = ???; 報錯


          ? 已知:聲明全局變量的時候不是用var 是隱式聲明

          ? 原理:當在局部作用域中聲明一個全局變量的時候,首先他會在局部作用域去找個變量,如果沒有,則向上查找 【全局作用域】,當找到了變量,則個這個變量重新賦值,如果沒有找到就聲明

          注意:必須要調(diào)用函數(shù)

          2.作用域的類型

          全局作用域

          局部作用域

          3.作用域鏈

          ? 說明

          在定義局部作用域的時候,聲明多個作用域,并且是包含關系的一個集合,調(diào)用函數(shù),叫做作用域的鏈

          ? 圖解

          ? 練習1

          運行結(jié)果:111

          運行結(jié)果:222

          ? 練習2

          上圖圖解結(jié)果:

          i:4
          J:5
          <script>
          var num = 111;
          function fn1() {
             var num = 222;
             function fn2(){
               num = 333;
               function fn3(){
                  num = 444;
               }
               fn3();
             }
             console.log(num);
             fn2();
           console.log(num);
          }
          fn1();
          console.log(num);
          </script>

          4.變量訪問的優(yōu)先級

          <script>
          var str='字符串1';
          function fun(str){
           var str='字符串2';
           function str(){
             str = '字符串3'
           }
           console.log(str);
          }
          fun();
          </script>


          結(jié)論:局部變量>局部函數(shù)>形參>全局變量

          5.總結(jié)

          ? 作用域概念:規(guī)定變量和函數(shù)的作用范圍,在那個區(qū)域調(diào)用不會出現(xiàn)JS錯誤

          ? 類型

          局部作用域

          全局作用域

          ? 作用域鏈

          一些函數(shù)的集合

          調(diào)用的時候,如果局部作用域聲明了全局變量,那么這個時候會向上查找,一直找到為止,如果找到重新復制,沒有找到則聲明。如果找到的是一個局部變量【var】 ,則停止向上查找

          ? 變量訪問的優(yōu)先級別

          局部作用域下的全局變量>函數(shù)>函數(shù)形參>全局變量

          注意:

          全局變量

          全局作用域

          局部變量

          局部作用域

          BOM 瀏覽器對象

          1.BOM簡介

          Browser object model 【BOM】 瀏覽器對象模型

          2.BOM作用

          我們JS有能力來操作瀏覽器

          3.BOM的分類

          Window 瀏覽器操作對象, 對象想的頂層

          Location 地址欄操作對象

          History 操作對象

          Document 網(wǎng)頁操作對象

          Screen 瀏覽器屏幕操作對象

          二、Window對象

          1.屬性和方法

          聲明的全局變量全部放在了window 對象中

          ? 屬性:

          innerWidth 獲取網(wǎng)頁的寬度

          document.documentElement.clientWidth 兼容低版本瀏覽器

          innerHeight 獲取網(wǎng)頁的高度

          document.documentElement.clientHeight 兼容低版本瀏覽器

          說明:高度寬度受狀態(tài)欄的影響

          兼容性問題:

          IE 6/7/8 低版本瀏覽器

          高版本瀏覽器

          IE9 以上

          兼容性瀏覽器

          谷歌、火狐、蘋果瀏覽器

          ? 方法:

          alert

          prompt

          confirm

          open(要打開的網(wǎng)頁地址,窗口的名字,窗口的外觀設置參數(shù))

          close()

          print()

          2.延時器

          ? 設置延時器

          描述:

          當加載網(wǎng)頁的時候,需要讓程序等待一段時間在來執(zhí)行【只是執(zhí)行一次】

          語法:

          setTimeout(‘fun’,s);

          說明:

          Fun:要調(diào)用的函數(shù) fun “fun()”

          S: 毫秒

          返回值:當前的定時器的一個表示 。

          ? 清楚延時器

          語法:

          clearTimeout(t);

          功能:

          清楚延時器 定義的操作方法,終止其運行

          參數(shù):

          T: 定義延時器返回的參數(shù)

          ? 實例


          <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
          <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en">
          <head>
             <meta http-equiv="Content-Type" content="text/html;charset=UTF-8">
             <title>Document</title>
          </head>
          <body>
             <input onclick="fun();" type="button" value="點擊顯示" >
          </body>
          <script>
             function fun(){
                 //設置一個延時器
                 setTimeout(function(){
                     alert('彈出');
                 },5000);
                 
             }
             
          </script>
          </html>


          3.定時器setInterval

          ? 設置定時器

          描述:

          當訪問網(wǎng)頁的時候,定時執(zhí)行一段代碼【執(zhí)行多次】

          語法:

          setInterval(‘fun’,s);

          說明:

          同上

          ? 清楚定時器

          描述:

          清楚定時器,不讓代碼繼續(xù)執(zhí)行

          語法:

          clearInterval(t);

          ? 案例:

          點擊發(fā)送倒計時效果

          1.點擊按鈕怎加點擊事件

          2.修改按鈕里面的內(nèi)容【value=”重新獲取驗證碼(事件)”】

          3.讓按鈕不能再次進行點擊【disabled 按鈕的一個屬性】

          4.增加倒計時的效果

          4.setTimeout和setInterval的區(qū)別

          setTimeout 執(zhí)行一次

          setInterval 執(zhí)行多次

          三、screen屏幕對象

          操作屬性:

          Screen.width 獲取屏幕的寬度

          Screen.height 獲取屏幕的高度

          screen.availWidth 不包含任務欄

          screen.availHeight 不包含任務欄

          四、navigator對象

          說明:瀏覽器的信息對象

          appName : 瀏覽器發(fā)行型的版本

          appVersion: 用戶瀏覽器的版本信息

          userAgent: 用戶使用的瀏覽器的信息

          使用:UserAgent 可以獲取用戶的詳細訪問信息,通過郵件的形式,將用戶的報錯發(fā)送到郵件中區(qū), 這樣我們可以快速定位問題所在

          五、location 對象

          屬性

          Location.href 瀏覽器地址的重定向

          方法:

          Location.reload(); 刷新當前頁面 , loaction.href = location.href ;

          六、history 對象

          用來記錄用戶訪問了哪些網(wǎng)頁

          屬性

          Length 長度 : 記錄瀏覽的個數(shù)

          方法:

          History.go(-1) 正數(shù)代表的是URL 向前, 負數(shù) URL向后

          History.back(); 一步一步的返回 <-

          History.forward(); 前進->

          DOM文檔對象模型

          1.DOM簡介

          Document object model 文檔對象模型

          2.DOM作用

          使JS有操作HTML的能力

          3.DOM的分類

          ? 核心 DOM

          對所有的DOM對象進行一個節(jié)點操作

          增加節(jié)點,查找節(jié)點

          ? HTML DOM

          對HTML下的節(jié)點進行查找, 快速定位。增加節(jié)點等。。。

          ? CSS DOM

          節(jié)點有CSS屬性 style=’樣式’

          也有直接操作樣式的屬性和方法

          節(jié)點對象.style.樣式屬性有JS轉(zhuǎn)換=屬性的值

          ? 事件 DOM

          節(jié)點中的onclick /onchange ....

          JS 直接操作的事件對象和方法

          三、核心DOM(1)

          1.DOM的分層結(jié)構(gòu) / 樹形結(jié)構(gòu)(節(jié)點樹)

          ? 概念

          在HTML中,將結(jié)構(gòu)使用一個類似于 倒立的樹狀結(jié)構(gòu)

          ? 圖解

          2、節(jié)點關系 (概念)

          根節(jié)點 HTML 一個頁面只能有一個跟節(jié)點

          子節(jié)點 某個節(jié)點的下一級節(jié)點

          父節(jié)點 某個子節(jié)點的上一級

          兄弟節(jié)點 并列關系的兩個節(jié)點

          空白節(jié)點 :空格、換行

          3、節(jié)點操作公共的屬性(語法)

          1)取子節(jié)點

          節(jié)點對象.firstChild 獲取節(jié)點的第一個子節(jié)點

          節(jié)點對象. lastChild 獲取節(jié)點的最后一個子節(jié)點

          節(jié)點對象.children 獲取節(jié)點下的所有子節(jié)點 不包含空白節(jié)點

          節(jié)點對象.childNodes 獲取節(jié)點下的所有子節(jié)點 包含空白節(jié)點

          2)獲取兄弟節(jié)點

          對象.nextSibling 獲取節(jié)點的下一個兄弟節(jié)點

          對象.previousSibling 獲取節(jié)點的上一個兄弟節(jié)點

          3)獲取父節(jié)點

          對象.parentNode 獲取上一級的節(jié)點

          4)獲取文本節(jié)點的值

          對象.nodeName 獲取選中的節(jié)點的名稱

          對象.nodeValue 獲取選中的節(jié)點的值

          ? 思考:獲取表單的值用什么?

          ? 案例

          獲取小紅文本節(jié)點,并彈出值

          <div><ul><li>老王</li><li>小紅</li><li>隔壁老王</li></ul></div>

          案例:




          四、HTML DOM【★】

          1.獲取元素節(jié)點的四種方法

          1)語法

          document.getElementById(‘id屬性的值’);

          作用:根據(jù)節(jié)點里面的ID的屬性來獲取節(jié)點對象

          document.getElementsByClassName(‘類名’)

          作用:根據(jù)節(jié)點里面的class的屬性來獲取節(jié)點對象

          document.getElementsByTagName(‘標簽名稱’)

          作用:根據(jù)標簽名稱來獲取要操作的標簽對象

          document.getElementsByName(‘屬性名稱’);

          作用:根據(jù)標簽的屬性來獲取要操作的標簽對象

          2)練習

          <div id="first">床前明月光</div>
          <div class="second">地上鞋兩雙</div>
          <p class="second" name='nameValue'>自己去想象</p>


          2.對節(jié)點的屬性操作

          1)語法

          對象.setAttribute(); //對節(jié)點對象增加屬性

          對象.removeAttribute(); //刪除節(jié)點屬性

          對象.getAttribute(“屬性名稱”); //獲取節(jié)點屬性的值

          2)練習

          ? 思考:通過獲取網(wǎng)頁指定節(jié)點返回的對象.屬性名獲取屬性值和getAttribute有什么區(qū)別?

          使用對象.屬性名稱 只能獲取系統(tǒng)預定義的屬性, 但是使用 getAttribute 這個方法可以獲取自定義屬性

          3、獲取元素內(nèi)容屬性

          1)說明

          對象.innerHTML //獲取包含了html標簽的內(nèi)容

          對象.innerText //獲取文本內(nèi)容,將標簽過濾

          2)練習

          自覺完成

          五、核心DOM(2)【★】

          1.節(jié)點創(chuàng)建

          對象.createElement(‘標簽名稱’); //創(chuàng)建節(jié)點對象

          對象.createTextNode(‘標簽里面的內(nèi)容’); //給節(jié)點對象增加內(nèi)容

          2.節(jié)點追加

          對象.appendChild(‘追加的內(nèi)容’); 最后面增加節(jié)點

          對象.insertBefore(新節(jié)點對象,舊幾點對象) //追加到節(jié)點的前面

          3.節(jié)點刪除

          對象.removeChild(); 刪除子節(jié)點

          ? 課堂案例

          給下面的詩增加標題《春曉》,并且在后面追加最后一句詩

          <ul>

          <li>春眠不覺曉</li>

          <li>處處蚊子咬</li>

          <li>點上蚊香后</li>

          <ul>

          <script>
             objUl = document.getElementsByTagName('ul');
             //獲取具體的操作節(jié)點
             ul1 = objUl[0];
             //創(chuàng)建節(jié)點
             tmpObj = document.createElement('li');
             //給臨時的節(jié)點增加內(nèi)容
              tmp2Obj = document.createTextNode("全部死光光");
             //將內(nèi)容追加進去
             tmpObj.appendChild(tmp2Obj);
             //增加新的li節(jié)點
             ul1.appendChild(tmpObj);
             
             //創(chuàng)建頭部標簽
             tmpObj = document.createElement('li');
             //頭部內(nèi)容
             tmp2Obj = document.createTextNode('<春曉>');
             //把內(nèi)容加到對象中區(qū)
             tmpObj.appendChild(tmp2Obj);
             console.log(ul1);
             console.log(ul1.children);
             //將創(chuàng)建的節(jié)點寫入到ul中去
             ul1.insertBefore(tmpObj,ul1.children[0]);
             
             console.log(objUl);
             
             
          </script>

          六、CSS DOM

          1.功能

          使用 JS 的內(nèi)置操作樣式屬性來改變DOM的樣式

          2.語法

          節(jié)點對象.style.JS的樣式屬性=’樣式的值’;

          CSS定義: Font-Size:14px;

          JS 定義 : fontSize = 14px;

          3.CSS屬性轉(zhuǎn)換JS規(guī)則

          如果CSS樣式?jīng)]有“-” 那么我們直接可以來使用

          如果CSS樣式有“-”那么我們直接去掉“-”將后面的單詞的首字字母大寫即可

          七、事件(event) DOM

          1、什么是事件

          用戶對網(wǎng)頁的所有操作稱之為事件

          2、事件類別

          1)鼠標事件

          2)鍵盤事件

          3)表單事件

          4)焦點事件

          5)UI事件

          Onload使用說明:用戶打開一個網(wǎng)頁,進入網(wǎng)頁的加載過程,如果使用了onload , 那么,等頁面中的所有HTML結(jié)構(gòu)全部加載完成后來運行

          設置事件

          <a 事件=“函數(shù)(值)”></a> //在標簽中的事件綁定

          <script> 對象.事件=function(){}</script> //設置事件

          <script> 對象.事件=null</script> //取消事件

          1)設置(取消)DOM1級事件

          多學一招 onclick=”函數(shù)(this)”

          2)設置(取消)DOM2級事件

          ? 語法

          ie6、7、8

          添加DOM2級:attachEvent(事件名稱,事件流);

          取消DOM2級:dectachEvent(‘事件名稱’);

          主流瀏覽器

          添加DOM2級:addEventListener(“事件名稱”,事件流);

          取消DOM2級:removeEventListener(“事件名稱”);

          腳下留心:

          如果在主流的瀏覽器使用的時候click 是不加 on

          在低版本的的瀏覽器中使用onclick 要加on

          3)添加同類型的事件

          通過DOM1級添加同類型的事件,只

          一級事件會進行覆蓋,最后定義的會覆蓋前面定義的

          通過DOM2級添加的同類型事件,


          4、DOM2級事件好處

          定義的每個事件都會執(zhí)行一次

          1、什么是事件流

          多個事件的集合,統(tǒng)稱為時間流

          ︴案例

          2.事件流深入理解

          1)事件流種類

          冒泡事件 : 事件是由內(nèi)向外

          捕獲類型 : 事件是由外到內(nèi)

          2)說明

          在主瀏覽器中使用 addEventListener(事件類型,處理函數(shù),true) ;才會有捕獲類型。

          在版本的瀏覽器中沒有捕獲類型,只有冒泡類型。

          DOM1級是冒泡類型

          3)驗證

          ? DOM1級就是冒泡型事件,無法改變


          ? DOM2級事件IE9以下統(tǒng)一冒泡型事件,無法改變


          ? DOM2級事件IE9及以上,默認冒泡事件;可通過更改第三個參數(shù)動態(tài)設置事件事件流:true表示捕捉型,false表示冒泡型

          前言

          JavaScript在百度一直有著廣泛的應用,特別是在瀏覽器端的行為管理。本文檔的目標是使JavaScript代碼風格保持一致,容易被理解和被維護。

          雖然本文檔是針對JavaScript設計的,但是在使用各種JavaScript的預編譯語言時(如TypeScript等)時,適用的部分也應盡量遵循本文檔的約定。

          2 代碼風格

          2.1 文件

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

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

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

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

          [強制] 使用 4 個空格做為一個縮進層級,不允許使用 2 個空格 或 tab 字符。

          [強制] switch 下的 case 和 default 必須增加一個縮進層級。

          // 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...
          }
          

          [強制] 二元運算符兩側(cè)必須有一個空格,一元運算符與操作對象之間不允許有空格。

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

          [強制] 用作代碼塊起始的左花括號 { 前必須有一個空格。

          示例:

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

          [強制] if / else / for / while / function / switch / do / try / catch / finally 關鍵字后,必須有一個空格。

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

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

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

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

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

          [強制] , 和 ; 前不允許有空格。

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

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

          // 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-- ) {
          }
          

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

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

          // 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};
          

          [強制] 行尾不得有多余的空格。

          [強制] 每個獨立語句結(jié)束后必須換行。

          [強制] 每行不得超過 120 個字符。

          解釋:超長的不可分割的代碼允許例外,比如復雜的正則表達式。長字符串不在例外之列。

          [強制] 運算符處換行時,運算符必須在新行的行首。

          // 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;
          

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

          // 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
          );
          

          [建議] 不同行為或邏輯的語句集,使用空行隔開,更易閱讀。

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

          [建議] 在語句的行長度超過 120 時,根據(jù)邏輯條件合理縮進。

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

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

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

          [強制] 不得省略語句結(jié)束的分號。

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

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

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

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

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

          解釋:IIFE = Immediately-Invoked Function Expression.

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

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

          2.3 命名

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

          [強制] 變量 使用 Camel命名法。

          var loadingModules = {};
          

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

          var HTML_ENTITY = {};
          

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

          function stringFormat(source) {
          }
          

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

          function hear(theBells) {
          }
          

          [強制] 類 使用 Pascal命名法。

          function TextNode(options) {
          }
          

          [強制] 類的 方法 / 屬性 使用 Camel命名法。

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

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

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

          [強制] 命名空間 使用 Camel命名法。

          equipments.heavyWeapons = {};
          

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

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

          [強制] 類名 使用 名詞。

          function Engine(options) {
          }
          

          [建議] 函數(shù)名 使用 動賓短語。

          function getStyle(element) {
          }
          

          [建議] boolean 類型的變量使用 is 或 has 開頭。

          var isReady = false;
          var hasMoreCommands = false;
          

          [建議] Promise對象 用 動賓短語的進行時 表達。

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

          2.4 注釋

          2.4.1 單行注釋

          [強制] 必須獨占一行。// 后跟一個空格,縮進與下一行被注釋說明的代碼一致。

          2.4.2 多行注釋

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

          2.4.3 文檔化注釋

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

          解釋:

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

          [強制] 文檔注釋前必須空一行。

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

          2.4.4 類型定義

          [強制] 類型定義都是以{開始, 以}結(jié)束。

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

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

          [強制] 對于基本類型 {string}, {number}, {boolean},首字母必須小寫。

          類型定義 語法示例 解釋 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 變長參數(shù), ...為類型前綴 任意類型 {*} 任意類型 可選任意類型 @param {*=} name 可選參數(shù),類型不限 可變?nèi)我忸愋?@param {...*} args 變長參數(shù),類型不限 2.4.5 文件注釋

          [強制] 文件頂部必須包含文件注釋,用 @file 標識文件說明。

          /**
           * @file Describe the file
           */
          

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

          解釋:

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

          @author 標識具有多人時,原則是按照 責任 進行排序。通常的說就是如果有問題,就是找第一個人應該比找第二個人有效。比如文件的創(chuàng)建者由于各種原因,模塊移交給了其他人或其他團隊,后來因為新增需求,其他人在新增代碼時,添加 @author 標識應該把自己的名字添加在創(chuàng)建人的前面。

          @author 中的名字不允許被刪除。任何勞動成果都應該被尊重。

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

          對于業(yè)務邏輯無關的技術(shù)型基礎項目,特別是開源的公共項目,應使用 @author 標識。

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

          2.4.6 命名空間注釋

          [建議] 命名空間使用 @namespace 標識。

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

          2.4.7 類注釋

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

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

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

          [建議] 使用 @extends 標記類的繼承信息。

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

          [強制] 使用包裝方式擴展類成員時, 必須通過 @lends 進行重新指向。

          解釋:沒有 @lends 標記將無法為該類生成包含擴展類成員的文檔。

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

          [強制] 類的屬性或方法等成員信息使用 @public / @protected / @private 中的任意一個,指明可訪問性。

          解釋:生成的文檔中將有可訪問性的標記,避免用戶直接使用非 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ù)/方法注釋

          [強制] 函數(shù)/方法注釋必須包含函數(shù)說明,有參數(shù)和返回值時必須使用注釋標識。

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

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

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

          [強制] 對 Object 中各項的描述, 必須使用 @param 標識。

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

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

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

          2.4.9 事件注釋

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

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

          [強制] 在會廣播事件的函數(shù)前使用 @fires 標識廣播的事件,在廣播事件代碼前使用 @event 標識事件。

          [建議] 對于事件對象的注釋,使用 @param 標識,生成文檔時可讀性更好。

          /**
           * 點擊處理
           *
           * @fires Select#change
           * @private
           */
          Select.prototype.clickHandler = function () {
           /**
           * 值變更時觸發(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 常量注釋

          [強制] 常量必須使用 @const 標記,并包含說明和類型信息。

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

          2.4.11 復雜類型注釋

          [建議] 對于類型未定義的復雜結(jié)構(gòu)的注釋,可以使用 @typedef 標識來定義。

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

          2.4.12 AMD 模塊注釋

          [強制] AMD 模塊使用 @module 或 @exports 標識。

          解釋:@exports 與 @module 都可以用來標識模塊,區(qū)別在于 @module 可以省略模塊名稱。而只使用 @exports 時在 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 標識:

          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;
           }
          );
          

          [強制] 對于已使用 @module 標識為 AMD模塊 的引用,在 namepaths 中必須增加 module: 作前綴。

          解釋:namepaths 沒有 module: 前綴時,生成的文檔中將無法正確生成鏈接。

          /**
           * 點擊處理
           *
           * @fires module:Select#change
           * @private
           */
          Select.prototype.clickHandler = function () {
           /**
           * 值變更時觸發(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'
           }
           );
          };
          

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

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

          [建議] 多模塊定義時,可以使用 @exports 標識各個模塊。

          // 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;
           }
          );
          

          [建議] 對于 exports 為 Object 的模塊,可以使用@namespace標識。

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

          [建議] 對于 exports 為類名的模塊,使用 @class 和 @exports 標識。

          // 只使用 @class Bar 時,類方法和屬性都必須增加 @name Bar#methodName 來標識,與 @exports 配合可以免除這一麻煩,并且在引用時可以省去 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 細節(jié)注釋

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

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

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

          [強制] 有時我們會使用一些特殊標記進行說明。特殊標記必須使用單行注釋的形式。下面列舉了一些常用標記:

          解釋:

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

          3 語言特性

          3.1 變量

          [強制] 變量在使用前必須通過 var 定義。

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

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

          [強制] 每個 var 只能聲明一個變量。

          解釋:一個 var 聲明多個變量,容易導致較長的行長度,并且在修改時容易造成逗號和分號的混淆。

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

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

          解釋: 變量聲明與使用的距離越遠,出現(xiàn)的跨度越大,代碼的閱讀與維護成本越高。雖然JavaScript的變量是函數(shù)作用域,還是應該根據(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 條件

          [強制] 在 Equality Expression 中使用類型嚴格的 ===。僅當判斷 null 或 undefined 時,允許使用 == null。

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

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

          [建議] 盡可能使用簡潔的表達式。

          // 字符串為空
          // 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. 閱讀的人容易找到最常見的情況,增加可讀性。
          2. 提高執(zhí)行效率。

          [建議] 對于相同變量或表達式的多值條件,用 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 塊后沒有任何語句,可以刪除 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ù)表達式,事先將函數(shù)提取到循環(huán)體外。

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

          // 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 () {});
          }
          

          [建議] 對循環(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';
           // ......
          }
          

          [建議] 對有序集合進行遍歷時,緩存 length。

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

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

          [建議] 對有序集合進行順序無關的遍歷時,使用逆序遍歷。

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

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

          3.4 類型

          3.4.1 類型檢測

          [建議] 類型檢測優(yōu)先使用 typeof。對象類型檢測使用 instanceof。null 或 undefined 的檢測使用 == 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 時,使用 + ''。

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

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

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

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

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

          [強制] 使用 parseInt 時,必須指定進制。

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

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

          var num = 3.14;
          !!num;
          

          [建議] number 去除小數(shù)點,使用 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 字符串

          [強制] 字符串開頭和結(jié)束使用單引號 '。

          解釋:

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

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

          解釋:

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

          示例:

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

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

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

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

          3.6 對象

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

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

          [強制] 對象創(chuàng)建時,如果一個對象的所有 屬性 均可以不添加引號,則所有 屬性 不得添加引號。

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

          [強制] 對象創(chuàng)建時,如果任何一個 屬性 需要添加引號,則所有 屬性 必須添加 '。

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

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

          [強制] 不允許修改和擴展任何原生對象和宿主對象的原型。

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

          [建議] 屬性訪問時,盡量使用 .。

          解釋:屬性名符合 Identifier 的要求,就可以通過 . 來訪問,否則就只能通過 [expr] 方式訪問。

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

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

          [建議] for in 遍歷對象時, 使用 hasOwnProperty 過濾掉原型中的屬性。

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

          3.7 數(shù)組

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

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

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

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

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

          [建議] 不因為性能的原因自己實現(xiàn)數(shù)組排序功能,盡量使用數(shù)組的 sort 方法。

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

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

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

          3.8 函數(shù)

          3.8.1 函數(shù)長度

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

          解釋:將過多的邏輯單元混在一個大函數(shù)中,易導致難以維護。一個清晰易懂的函數(shù)應該完成單一的邏輯單元。復雜的操作應進一步抽取,通過函數(shù)的調(diào)用來體現(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ù)會難以明確其主線邏輯,因此下方是一種更合理的表達方式:
          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ù)的參數(shù)控制在 6 個以內(nèi)。

          解釋:

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

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

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

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

          如下函數(shù):

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

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

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

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

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

          3.8.3 閉包

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

          解釋:

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

          閉包會阻止一些變量的垃圾回收,對于較老舊的JavaScript引擎,可能導致外部所有變量均無法回收。

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

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

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

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

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

          對于Chakra引擎,暫無法得知是按 V8 的模式還是按 SpiderMonkey 的模式進行。

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

          [建議] 使用 IIFE 避免 Lift 效應。

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

          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]();
          }
          

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

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

          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 () {};
          

          [建議] 對于性能有高要求的場合,建議存在一個空函數(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>

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

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

          /**
           * 構(gòu)建類之間的繼承關系
           * 
           * @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;
          }
          

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

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

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

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

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

          [強制] 自定義事件的 事件名 必須全小寫。

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

          [強制] 自定義事件只能有一個 event 參數(shù)。如果事件需要傳遞較多信息,應仔細設計事件對象。

          解釋:一個事件對象的好處有:

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

          [建議] 設計自定義事件時,應考慮禁止默認行為。

          解釋:常見禁止默認行為的方式有兩種:

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

          3.10 動態(tài)特性

          3.10.1 eval

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

          解釋:直接 eval,指的是以函數(shù)方式調(diào)用 eval 的調(diào)用方法。直接 eval 調(diào)用執(zhí)行代碼的作用域為本地作用域,應當避免。

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

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

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

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

          解釋:通過 new Function 生成的函數(shù)作用域是全局使用域,不會影響當當前的本地作用域。如果有動態(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 可能會增加代碼的復雜度,不利于閱讀和管理;也會對性能有影響。大多數(shù)使用 with 的場景都能使用其他方式較好的替代。所以,盡量不要使用 with。

          3.10.4 delete

          [建議] 減少 delete 的使用。

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

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

          解釋:

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

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

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

          3.10.5 對象屬性

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

          解釋:

          JavaScript 因其腳本語言的動態(tài)特性,當一個對象未被 seal 或 freeze 時,可以任意添加、刪除、修改屬性值。

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

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

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

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

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

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

          [建議] 具備強類型的設計。

          解釋:

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

          4 瀏覽器環(huán)境

          4.1 模塊化

          4.1.1 AMD

          [強制] 使用 AMD 作為模塊定義。

          解釋:

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

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

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

          [強制] 模塊 id 必須符合標準。

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

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

          4.1.2 define

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

          解釋:

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

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

          define(
           function (require) {
           }
          );
          

          [建議] 使用 return 來返回模塊定義。

          解釋:使用 return 可以減少 factory 接收的參數(shù)(不需要接收 exports 和 module),在沒有 AMD Loader 的場景下也更容易進行簡單的處理來偽造一個 Loader。

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

          4.1.3 require

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

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

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

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

          解釋:

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

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

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

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

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

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

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

          4.2 DOM

          4.2.1 元素獲取

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

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

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

          解釋:原生獲取元素集合的結(jié)果并不直接引用 DOM 元素,而是對索引進行讀取,所以 DOM 結(jié)構(gòu)的改變會實時反映到結(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>
          

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

          4.2.2 樣式獲取

          [建議] 獲取元素實際樣式信息時,應使用 getComputedStyle 或 currentStyle。

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

          4.2.3 樣式設置

          [建議] 盡可能通過為元素添加預定義的 className 來改變元素樣式,避免直接操作 style 設置。

          [強制] 通過 style 對象設置元素樣式時,對于帶單位非 0 值的屬性,不允許省略單位。

          解釋:除了 IE,標準瀏覽器會忽略不規(guī)范的屬性值,導致兼容性問題。

          4.2.4 DOM 操作

          [建議] 操作 DOM 時,盡量減少頁面 reflow。

          解釋:頁面 reflow 是非常耗時的行為,非常容易導致性能瓶頸。下面一些場景會觸發(fā)瀏覽器的reflow:

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

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

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

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

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

          4.2.5 DOM 事件

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

          解釋:expando 屬性綁定事件容易導致互相覆蓋。

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

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

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

          作者:前端切圖小弟,個人運營的公眾號:前端讀者(fe_duzhe)


          主站蜘蛛池模板: 色久综合网精品一区二区| 精品国产一区二区三区麻豆| 国产视频福利一区| 一区国严二区亚洲三区| 精品一区二区三区免费观看| 国产亚洲欧洲Aⅴ综合一区| 中文字幕一区二区三区在线播放| 精品一区二区在线观看| 四虎在线观看一区二区| 国产精品污WWW一区二区三区| 亚洲av成人一区二区三区观看在线| 日韩一区二区在线免费观看| 国产丝袜视频一区二区三区| 加勒比无码一区二区三区| 亚洲一区二区三区自拍公司| 国产在线一区观看| 日本一区二区三区在线看| 亚洲香蕉久久一区二区| 亚洲乱色熟女一区二区三区丝袜| 亚洲高清成人一区二区三区| 日本一区二区三区免费高清在线 | 激情无码亚洲一区二区三区| 国产精品乱码一区二区三区| 无码人妻啪啪一区二区| 性无码一区二区三区在线观看| 色窝窝免费一区二区三区| 久久久国产精品一区二区18禁| 一区二区三区精品视频| 无码国产精品一区二区免费16| 波多野结衣电影区一区二区三区 | 久久久久久人妻一区二区三区 | 无码日韩人妻AV一区二区三区| 精品国产一区二区22| 中文字幕在线观看一区二区| 久久精品黄AA片一区二区三区 | 色久综合网精品一区二区| 性色AV一区二区三区天美传媒| 国产在线观看精品一区二区三区91| 国产无人区一区二区三区| 欧美日韩精品一区二区在线观看 | 亚洲一区二区观看播放|