整合營銷服務商

          電腦端+手機端+微信端=數據同步管理

          免費咨詢熱線:

          JavaScript代碼風格參考指南

          JavaScript代碼風格參考指南

          碼必須盡可能的清晰和易讀。

          這實際上是一種編程藝術 —— 以一種正確并且人們易讀的方式編碼來完成一個復雜的任務。一個良好的代碼風格大大有助于實現這一點。

          一、語法

          下面是一個備忘單,其中列出了一些建議的規則(詳情請參閱下文):

          現在,讓我們詳細討論一下這些規則和它們的原因吧。

          沒有什么規則是“必須”的

          沒有什么規則是“刻在石頭上”的。這些是風格偏好,而不是宗教教條。

          二、花括號

          在大多數的 JavaScript 項目中,花括號以 “Egyptian” 風格(譯注:“egyptian” 風格又稱 K&R 風格 — 代碼段的開括號位于一行的末尾,而不是另起一行的風格)書寫,左花括號與相應的關鍵詞在同一行上 — 而不是新起一行。左括號前還應該有一個空格,如下所示:

          if (condition) {
            // do this
            // ...and that
            // ...and that
          }

          單行構造(如 if (condition) doSomething())也是一個重要的用例。我們是否應該使用花括號?如果是,那么在哪里?

          下面是這幾種情況的注釋,你可以自己判斷一下它們的可讀性:

          1. 初學者常這樣寫。非常不好!這里不需要花括號:if (n < 0) {alert(`Power ${n} is not supported`);}
          2. 拆分為單獨的行,不帶花括號。永遠不要這樣做,添加新行很容易出錯:if (n < 0) alert(`Power ${n} is not supported`);
          3. 寫成一行,不帶花括號 — 如果短的話,也是可以的:if (n < 0) alert(`Power ${n} is not supported`);
          4. 最好的方式:if (n < 0) { alert(`Power ${n} is not supported`); }

          對于很短的代碼,寫成一行是可以接受的:例如 if (cond) return null。但是代碼塊(最后一個示例)通常更具可讀性。

          三、行的長度

          沒有人喜歡讀一長串代碼,最好將代碼分割一下。

          例如:

          // 回勾引號 ` 允許將字符串拆分為多行
          let str=`
            ECMA International's TC39 is a group of JavaScript developers,
            implementers, academics, and more, collaborating with the community
            to maintain and evolve the definition of JavaScript.
          `;

          對于 if 語句:

          if (
            id===123 &&
            moonPhase==='Waning Gibbous' &&
            zodiacSign==='Libra'
          ) {
            letTheSorceryBegin();
          }

          一行代碼的最大長度應該在團隊層面上達成一致。通常是 80 或 120 個字符。

          四、縮進

          有兩種類型的縮進:

          • 水平方向上的縮進:2 或 4 個空格。一個水平縮進通常由 2 或 4 個空格或者 “Tab” 制表符(key Tab)構成。選擇哪一個方式是一場古老的圣戰。如今空格更普遍一點。選擇空格而不是 tabs 的優點之一是,這允許你做出比 “Tab” 制表符更加靈活的縮進配置。例如,我們可以將參數與左括號對齊,像下面這樣:
          show(parameters,
               aligned, // 5 spaces padding at the left
               one,
               after,
               another
            ) {
            // ...
          }
          • 垂直方向上的縮進:用于將代碼拆分成邏輯塊的空行。即使是單個函數通常也被分割為數個邏輯塊。在下面的示例中,初始化的變量、主循環結構和返回值都被垂直分割了:
          function pow(x, n) {
            let result=1;
            //              <--
            for (let i=0; i < n; i++) {
              result *=x;
            }
            //              <--
            return result;
          }


          插入一個額外的空行有助于使代碼更具可讀性。寫代碼時,不應該出現連續超過 9 行都沒有被垂直分割的代碼。

          五、分號

          每一個語句后面都應該有一個分號。即使它可以被跳過。

          有一些編程語言的分號確實是可選的,那些語言中也很少使用分號。但是在 JavaScript 中,極少數情況下,換行符有時不會被解釋為分號,這時代碼就容易出錯。更多內容請參閱 代碼結構 一章的內容。

          如果你是一個有經驗的 JavaScript 程序員,你可以選擇像 StandardJS 這樣的無分號的代碼風格。否則,最好使用分號以避免可能出現的陷阱。大多數開發人員都應該使用分號。

          六、嵌套的層級

          盡量避免代碼嵌套層級過深。

          例如,在循環中,有時候使用 continue 指令以避免額外的嵌套是一個好主意。

          例如,不應該像下面這樣添加嵌套的 if 條件:

          for (let i=0; i < 10; i++) {
            if (cond) {
              ... // <- 又一層嵌套
            }
          }

          我們可以這樣寫:

          for (let i=0; i < 10; i++) {
            if (!cond) continue;
            ...  // <- 沒有額外的嵌套
          } //多用這種風格。

          使用 if/elsereturn 也可以做類似的事情。

          例如,下面的兩個結構是相同的。

          第一個:

          function pow(x, n) {
            if (n < 0) {
              alert("Negative 'n' not supported");
            } else {
              let result=1;
          
              for (let i=0; i < n; i++) {
                result *=x;
              }
          
              return result;
            }
          }

          第二個:

          function pow(x, n) {
            if (n < 0) {
              alert("Negative 'n' not supported");
              return;
            }
          
            let result=1;
          
            for (let i=0; i < n; i++) {
              result *=x;
            }
          
            return result;
          }

          但是第二個更具可讀性,因為 n < 0 這個“特殊情況”在一開始就被處理了。一旦條件通過檢查,代碼執行就可以進入到“主”代碼流,而不需要額外的嵌套。

          七、函數位置

          如果你正在寫幾個“輔助”函數和一些使用它們的代碼,那么有三種方式來組織這些函數。

          1. 在調用這些函數的代碼的 上方 聲明這些函數:
          // function declarations
          function createElement() {
            ...
          }
          
          function setHandler(elem) {
            ...
          }
          
          function walkAround() {
            ...
          }
          
          // the code which uses them
          let elem=createElement();
          setHandler(elem);
          walkAround();
          1. 先寫調用代碼,再寫函數
          // the code which uses the functions
          let elem=createElement();
          setHandler(elem);
          walkAround();
          
          // --- helper functions ---
          function createElement() {
            ...
          }
          
          function setHandler(elem) {
            ...
          }
          
          function walkAround() {
            ...
          }
          1. 混合:在第一次使用一個函數時,對該函數進行聲明。

          大多數情況下,第二種方式更好。

          這是因為閱讀代碼時,我們首先想要知道的是“它做了什么”。如果代碼先行,那么在整個程序的最開始就展示出了這些信息。之后,可能我們就不需要閱讀這些函數了,尤其是它們的名字清晰地展示出了它們的功能的時候。

          八、風格指南

          風格指南包含了“如果編寫”代碼的通用規則,例如:使用哪個引號、用多少空格來縮進、一行代碼最大長度等非常多的細節。

          當團隊中的所有成員都使用相同的風格指南時,代碼看起來將是統一的。無論是團隊中誰寫的,都是一樣的風格。

          當然,一個團隊可以制定他們自己的風格指南,但是沒必要這樣做?,F在已經有了很多制定好的代碼風格指南可供選擇。

          一些受歡迎的選擇:

          • Google JavaScript 風格指南
          • Airbnb JavaScript 風格指南
          • Idiomatic.JS
          • StandardJS
          • 還有很多……

          如果你是一個初學者,你可以從本章中上面的內容開始。然后你可以瀏覽其他風格指南,并選擇一個你最喜歡的。

          九、自動檢查器

          檢查器(Linters)是可以自動檢查代碼樣式,并提出改進建議的工具。

          它們的妙處在于進行代碼風格檢查時,還可以發現一些代碼錯誤,例如變量或函數名中的錯別字。因此,即使你不想堅持某一種特定的代碼風格,我也建議你安裝一個檢查器。

          下面是一些最出名的代碼檢查工具:

          • JSLint — 第一批檢查器之一。
          • JSHint — 比 JSLint 多了更多設置。
          • ESLint — 應該是最新的一個。

          它們都能夠做好代碼檢查。我使用的是 ESLint。

          大多數檢查器都可以與編輯器集成在一起:只需在編輯器中啟用插件并配置代碼風格即可。

          例如,要使用 ESLint 你應該這樣做:

          1. 安裝 Node.JS。
          2. 使用 npm install -g eslint 命令(npm 是一個 JavaScript 包安裝工具)安裝 ESLint。
          3. 在你的 JavaScript 項目的根目錄(包含該項目的所有文件的那個文件夾)創建一個名為 .eslintrc 的配置文件。
          4. 在集成了 ESLint 的編輯器中安裝/啟用插件。大多數編輯器都有這個選項。

          下面是一個 .eslintrc 文件的例子:

          {
            "extends": "eslint:recommended",
            "env": {
              "browser": true,
              "node": true,
              "es6": true
            },
            "rules": {
              "no-console": 0,
              "indent": 2
            }
          }

          這里的 "extends" 指令表示我們是基于 “eslint:recommended” 的設置項而進行設置的。之后,我們制定我們自己的規則。

          你也可以從網上下載風格規則集并進行擴展。有關安裝的更多詳細信息

          此外,某些 IDE 有內置的檢查器,這非常方便,但是不像 ESLint 那樣可自定義。

          十、總結

          本文描述的(和提到的代碼風格指南中的)所有語法規則,都旨在幫助你提高代碼可讀性。它們都是值得商榷的。

          當我們思考如何寫“更好”的代碼的時候,我們應該問自己的問題是:“什么可以讓代碼可讀性更高,更容易被理解?”和“什么可以幫助我們避免錯誤?”這些是我們討論和選擇代碼風格時要牢記的主要原則。

          閱讀流行的代碼風格指南,可以幫助你了解有關代碼風格的變化趨勢和最佳實踐的最新想法。

          來了~歡迎轉發、收藏,記得點個贊唄。^_^

          無論當前 JavaScript 代碼是內嵌還是在外鏈文件中,頁面的下載和渲染都必須停下來等待腳本執行完成。JavaScript 執行過程耗時越久,瀏覽器等待響應用戶輸入的時間就越長。瀏覽器在下載和執行腳本時出現阻塞的原因在于,腳本可能會改變頁面或 JavaScript 的命名空間,它們對后面頁面內容造成影響。一個典型的例子就是在頁面中使用document.write()。例如清單 1。

          清單 1 JavaScript 代碼內嵌示例

          當瀏覽器遇到<script>標簽時,當前 HTML 頁面無從獲知 JavaScript 是否會向<p> 標簽添加內容,或引入其他元素,或甚至移除該標簽。因此,這時瀏覽器會停止處理頁面,先執行 JavaScript代碼,然后再繼續解析和渲染頁面。同樣的情況也發生在使用 src 屬性加載 JavaScript的過程中,瀏覽器必須先花時間下載外鏈文件中的代碼,然后解析并執行它。在這個過程中,頁面渲染和用戶交互完全被阻塞了。

          1.腳本位置

          HTML 4 規范指出 <script> 標簽可以放在 HTML 文檔的<head>或<body>中,并允許出現多次。Web 開發人員一般習慣在 <head> 中加載外鏈的 JavaScript,接著用 <link> 標簽用來加載外鏈的 CSS 文件或者其他頁面信息。例如清單 2。

          清單 2 低效率腳本位置示例

          然而這種常規的做法卻隱藏著嚴重的性能問題。在清單 2 的示例中,當瀏覽器解析到 <script> 標簽(第 4 行)時,瀏覽器會停止解析其后的內容,而優先下載腳本文件,并執行其中的代碼,這意味著,其后的 styles.css 樣式文件和<body>標簽都無法被加載,由于<body>標簽無法被加載,那么頁面自然就無法渲染了。因此在該 JavaScript 代碼完全執行完之前,頁面都是一片空白。圖 1 描述了頁面加載過程中腳本和樣式文件的下載過程。

          圖 1 JavaScript 文件的加載和執行阻塞其他文件的下載

          我們可以發現一個有趣的現象:第一個 JavaScript 文件開始下載,與此同時阻塞了頁面其他文件的下載。此外,從 script1.js 下載完成到 script2.js 開始下載前存在一個延時,這段時間正好是 script1.js 文件的執行過程。每個文件必須等到前一個文件下載并執行完成才會開始下載。在這些文件逐個下載過程中,用戶看到的是一片空白的頁面。

          從 IE 8、Firefox 3.5、Safari 4 和 Chrome 2 開始都允許并行下載 JavaScript 文件。這是個好消息,因為<script>標簽在下載外部資源時不會阻塞其他<script>標簽。遺憾的是,JavaScript 下載過程仍然會阻塞其他資源的下載,比如樣式文件和圖片。盡管腳本的下載過程不會互相影響,但頁面仍然必須等待所有 JavaScript 代碼下載并執行完成才能繼續。因此,盡管最新的瀏覽器通過允許并行下載提高了性能,但問題尚未完全解決,腳本阻塞仍然是一個問題。

          由于腳本會阻塞頁面其他資源的下載,因此推薦將所有<script>標簽盡可能放到<body>標簽的底部,以盡量減少對整個頁面下載的影響。例如清單 3

          清單 3 推薦的代碼放置位置示例

          這段代碼展示了在 HTML 文檔中放置<script>標簽的推薦位置。盡管腳本下載會阻塞另一個腳本,但是頁面的大部分內容都已經下載完成并顯示給了用戶,因此頁面下載不會顯得太慢。

          這是優化 JavaScript 的首要規則:將腳本放在底部。

          2.組織腳本

          由于每個<script>標簽初始下載時都會阻塞頁面渲染,所以減少頁面包含的<script>標簽數量有助于改善這一情況。這不僅針對外鏈腳本,內嵌腳本的數量同樣也要限制。瀏覽器在解析 HTML 頁面的過程中每遇到一個<script>標簽,都會因執行腳本而導致一定的延時,因此最小化延遲時間將會明顯改善頁面的總體性能。

          這個問題在處理外鏈 JavaScript 文件時略有不同。考慮到 HTTP 請求會帶來額外的性能開銷,因此下載單個 100Kb 的文件將比下載 5 個 20Kb 的文件更快。也就是說,減少頁面中外鏈腳本的數量將會改善性能。

          通常一個大型網站或應用需要依賴數個 JavaScript 文件。您可以把多個文件合并成一個,這樣只需要引用一個<script>標簽,就可以減少性能消耗。文件合并的工作可通過離線的打包工具或者一些實時的在線服務來實現。

          需要特別提醒的是,把一段內嵌腳本放在引用外鏈樣式表的<link>之后會導致頁面阻塞去等待樣式表的下載。這樣做是為了確保內嵌腳本在執行時能獲得最精確的樣式信息。因此,建議不要把內嵌腳本緊跟在<link>標簽后面。

          3.無阻塞的腳本

          減少 JavaScript 文件大小并限制 HTTP 請求數在功能豐富的 Web 應用或大型網站上并不總是可行。Web 應用的功能越豐富,所需要的 JavaScript 代碼就越多,盡管下載單個較大的 JavaScript 文件只產生一次 HTTP 請求,卻會鎖死瀏覽器的一大段時間。為避免這種情況,需要通過一些特定的技術向頁面中逐步加載 JavaScript 文件,這樣做在某種程度上來說不會阻塞瀏覽器。

          無阻塞腳本的秘訣在于,在頁面加載完成后才加載 JavaScript 代碼。這就意味著在 window 對象的 onload事件觸發后再下載腳本。有多種方式可以實現這一效果。

          3.1延遲加載腳本

          HTML 4 為<script>標簽定義了一個擴展屬性:defer。Defer 屬性指明本元素所含的腳本不會修改 DOM,因此代碼能安全地延遲執行。defer 屬性只被 IE 4 和 Firefox 3.5 更高版本的瀏覽器所支持,所以它不是一個理想的跨瀏覽器解決方案。在其他瀏覽器中,defer 屬性會被直接忽略,因此<script>標簽會以默認的方式處理,也就是說會造成阻塞。然而,如果您的目標瀏覽器支持的話,這仍然是個有用的解決方案。清單 4 是一個例子

          清單 4 defer 屬性使用方法示例

          <script type="text/javascript" src="script1.js" defer></script>

          帶有 defer 屬性的<script>標簽可以放置在文檔的任何位置。對應的 JavaScript 文件將在頁面解析到<script>標簽時開始下載,但不會執行,直到 DOM 加載完成,即onload事件觸發前才會被執行。當一個帶有 defer 屬性的 JavaScript 文件下載時,它不會阻塞瀏覽器的其他進程,因此這類文件可以與其他資源文件一起并行下載。

          任何帶有 defer 屬性的<script>元素在 DOM 完成加載之前都不會被執行,無論內嵌或者是外鏈腳本都是如此。清單 5 的例子展示了defer屬性如何影響腳本行為:

          清單 5 defer 屬性對腳本行為的影響

          這段代碼在頁面處理過程中彈出三次對話框。不支持 defer 屬性的瀏覽器的彈出順序是:"defer"、"script"、"load"。而在支持 defer 屬性的瀏覽器上,彈出的順序則是:"script"、"defer"、"load"。請注意,帶有 defer 屬性的<script>元素不是跟在第二個后面執行,而是在 onload 事件被觸發前被調用。

          如果您的目標瀏覽器只包括 Internet Explorer 和 Firefox 3.5,那么 defer 腳本確實有用。如果您需要支持跨領域的多種瀏覽器,那么還有更一致的實現方式。

          HTML 5 為<script>標簽定義了一個新的擴展屬性:async。它的作用和 defer 一樣,能夠異步地加載和執行腳本,不因為加載腳本而阻塞頁面的加載。但是有一點需要注意,在有 async 的情況下,JavaScript 腳本一旦下載好了就會執行,所以很有可能不是按照原本的順序來執行的。如果 JavaScript 腳本前后有依賴性,使用 async 就很有可能出現錯誤。

          3.2動態腳本元素

          文檔對象模型(DOM)允許您使用 JavaScript 動態創建 HTML 的幾乎全部文檔內容。<script>元素與頁面其他元素一樣,可以非常容易地通過標準 DOM 函數創建:

          清單 6 通過標準 DOM 函數創建<script>元素

          新的<script>元素加載 script1.js 源文件。此文件當元素添加到頁面之后立刻開始下載。此技術的重點在于:無論在何處啟動下載,文件的下載和運行都不會阻塞其他頁面處理過程。您甚至可以將這些代碼放在<head>部分而不會對其余部分的頁面代碼造成影響(除了用于下載文件的 HTTP 連接)。

          當文件使用動態腳本節點下載時,返回的代碼通常立即執行(除了 Firefox 和 Opera,他們將等待此前的所有動態腳本節點執行完畢)。當腳本是"自運行"類型時,這一機制運行正常,但是如果腳本只包含供頁面其他腳本調用調用的接口,則會帶來問題。這種情況下,您需要跟蹤腳本下載完成并是否準備妥善。可以使用動態 <script> 節點發出事件得到相關信息。

          Firefox、Opera, Chorme 和 Safari 3+會在<script>節點接收完成之后發出一個 onload 事件。您可以監聽這一事件,以得到腳本準備好的通知:

          清單 7 通過監聽 onload 事件加載 JavaScript 腳本

          Internet Explorer 支持另一種實現方式,它發出一個 readystatechange 事件。<script>元素有一個 readyState 屬性,它的值隨著下載外部文件的過程而改變。readyState 有五種取值:

          &middot; "uninitialized":默認狀態

          &middot; "loading":下載開始

          &middot; "loaded":下載完成

          &middot; "interactive":下載完成但尚不可用

          &middot; "complete":所有數據已經準備好

          微軟文檔上說,在<script>元素的生命周期中,readyState 的這些取值不一定全部出現,但并沒有指出哪些取值總會被用到。實踐中,我們最感興趣的是"loaded"和"complete"狀態。Internet Explorer 對這兩個 readyState 值所表示的最終狀態并不一致,有時<script>元素會得到"loader"卻從不出現"complete",但另外一些情況下出現"complete"而用不到"loaded"。最安全的辦法就是在 readystatechange 事件中檢查這兩種狀態,并且當其中一種狀態出現時,刪除 readystatechange 事件句柄(保證事件不會被處理兩次):

          清單 8 通過檢查 readyState 狀態加載 JavaScript 腳本

          大多數情況下,您希望調用一個函數就可以實現 JavaScript 文件的動態加載。下面的函數封裝了標準實現和 IE 實現所需的功能:

          清單 9 通過函數進行封裝

          此函數接收兩個參數:JavaScript 文件的 URL,和一個當 JavaScript 接收完成時觸發的回調函數。屬性檢查用于決定監視哪種事件。最后一步,設置 src 屬性,并將<script>元素添加至頁面。此 loadScript() 函數使用方法如下:

          清單 10 loadScript()函數使用方法

          您可以在頁面中動態加載很多 JavaScript 文件,但要注意,瀏覽器不保證文件加載的順序。所有主流瀏覽器之中,只有 Firefox 和 Opera 保證腳本按照您指定的順序執行。其他瀏覽器將按照服務器返回它們的次序下載并運行不同的代碼文件。您可以將下載操作串聯在一起以保證他們的次序,如下:

          清單 11 通過 loadScript()函數加載多個 JavaScript 腳本

          此代碼等待 script1.js 可用之后才開始加載 script2.js,等 script2.js 可用之后才開始加載 script3.js。雖然此方法可行,但如果要下載和執行的文件很多,還是有些麻煩。如果多個文件的次序十分重要,更好的辦法是將這些文件按照正確的次序連接成一個文件。獨立文件可以一次性下載所有代碼(由于這是異步進行的,使用一個大文件并沒有什么損失)。

          動態腳本加載是非阻塞 JavaScript 下載中最常用的模式,因為它可以跨瀏覽器,而且簡單易用。

          3.3使用 XMLHttpRequest(XHR)對象

          此技術首先創建一個 XHR 對象,然后下載 JavaScript 文件,接著用一個動態 <script> 元素將 JavaScript 代碼注入頁面。清單 12 是一個簡單的例子:

          清單 12 通過 XHR 對象加載 JavaScript 腳本

          此代碼向服務器發送一個獲取 script1.js 文件的 GET 請求。onreadystatechange 事件處理函數檢查 readyState 是不是 4,然后檢查 HTTP 狀態碼是不是有效(2XX 表示有效的回應,304 表示一個緩存響應)。如果收到了一個有效的響應,那么就創建一個新的<script>元素,將它的文本屬性設置為從服務器接收到的 responseText 字符串。這樣做實際上會創建一個帶有內聯代碼的<script>元素。一旦新<script>元素被添加到文檔,代碼將被執行,并準備使用。

          這種方法的主要優點是,您可以下載不立即執行的 JavaScript 代碼。由于代碼返回在<script>標簽之外(換句話說不受<script>標簽約束),它下載后不會自動執行,這使得您可以推遲執行,直到一切都準備好了。另一個優點是,同樣的代碼在所有現代瀏覽器中都不會引發異常。

          此方法最主要的限制是:JavaScript 文件必須與頁面放置在同一個域內,不能從 CDN 下載(CDN 指"內容投遞網絡(Content Delivery Network)",所以大型網頁通常不采用 XHR 腳本注入技術。

          4.總結

          一般而言,減少 JavaScript 對性能的影響有以下幾種方法:

          &middot; A.將所有的<script>標簽放到頁面底部,也就是</body>閉合標簽之前,這能確保在腳本執行前頁面已經完成了渲染。

          &middot; B.盡可能地合并腳本。頁面中的<script>標簽越少,加載也就越快,響應也越迅速。無論是外鏈腳本還是內嵌腳本都是如此。

          &middot; C.采用無阻塞下載 JavaScript 腳本的方法:

          &middot; a-使用<script>標簽的 defer 屬性(僅適用于 IE 和 Firefox 3.5 以上版本);

          &middot; b-使用動態創建的<script>元素來下載并執行代碼;

          &middot; c-使用 XHR 對象下載 JavaScript 代碼并注入頁面中。

          通過以上策略,可以在很大程度上提高那些需要使用大量 JavaScript 的 Web 網站和應用的實際性能。


          看完了,別忘了收藏、轉發、點個贊,更別忘了要關注本號!^_^

          在,終于有可能在瀏覽器中運行人臉識別了!通過本文,我將介紹face-api,它是一個構建在tensorflow.js core的JavaScript模塊,它實現了人臉檢測、人臉識別和人臉地標檢測三種類型的CNN。

          我們首先研究一個簡單的代碼示例,以用幾行代碼立即開始使用該包。

          第一個face-recognition.js,現在又是一個包?

          如果你讀過關于人臉識別與其他的NodeJS文章:(https://medium.com/@muehler.v/node-js-face-recognition-js-simple-and-robust-face-recognition-using-deep-learning-ea5ba8e852),你可能知道,不久前,我組裝了一個類似的包(face-recognition.js)。

          起初,我沒有想到在javascript社區中對臉部識別軟件包的需求如此之高。對于很多人來說,face-recognition.js似乎是一個不錯的免費使用且開源的替代付費服務的人臉識別服務,就像微軟或亞馬遜提供的一樣。其中很多人問,是否可以在瀏覽器中完全運行完整的人臉識別管道。

          在這里,我應該感謝tensorflow.js!我設法使用tfjs-core實現了部分類似的工具,這使你可以在瀏覽器中獲得與face-recognition.js幾乎相同的結果!最好的部分是,不需要設置任何外部依賴關系,可以直接使用。并且,它是GPU加速的,在WebGL上運行操作。

          這使我相信,JavaScript社區需要這樣的瀏覽器包!你可以用這個來構建自己的各種各樣的應用程序。;)

          如何用深度學習解決人臉識別問題

          如果你希望盡快開始,也可以直接去編碼。但想要更好地理解face-api.js中用于實現人臉識別的方法,我強烈建議你看一看,這里有很多我經常被問到的問題。

          簡單地說,我們真正想要實現的是,識別一個人的面部圖像(input image)。我們這樣做的方式是為每個我們想要識別的人提供一個(或多個)圖像,并標注人名(reference data)?,F在我們將它們進行比較,并找到最相似的參考圖像。如果兩張圖片足夠相似,我們輸出該人的姓名,否則我們輸出“unknown”。

          聽起來不錯吧!然而,還是存在兩個問題。首先,如果我們有一張顯示多個人的圖片,我們想要識別所有的人,該怎么辦?其次,我們需要能夠獲得這種類型的兩張人臉圖像的相似性度量,以便比較它們......

          人臉檢測

          第一個問題的答案是人臉檢測。簡而言之,我們將首先找到輸入圖像中的所有人臉。對于人臉檢測,face-api.js實現了SSD(Single Shot Multibox Detector),它基本上是基于MobileNetV1的CNN,只是在網絡頂部疊加了一些額外的盒預測層。

          網絡返回每個人臉的邊界框及其相應的分數,即每個邊界框顯示一個人臉的可能性。分數用于過濾邊界框,因為圖像中可能根本不包含任何人臉。請注意,即使只有一個人檢索邊界框,也應執行人臉檢測。

          人臉標志檢測和人臉對齊

          第一個問題解決了!但是,我們希望對齊邊界框,這樣我們就可以在每個框的人臉中心提取出圖像,然后將它們傳遞給人臉識別網絡,這會使人臉識別更加準確!

          為此,face-api.js實現了一個簡單的CNN,它返回給定人臉圖像的68個點的人臉標志:

          從地標位置,邊界框可以準確的包圍人臉。在下圖,你可以看到人臉檢測的結果(左)與對齊的人臉圖像(右)的比較:

          人臉識別

          現在我們可以將提取并對齊的人臉圖像提供給人臉識別網絡,這個網絡基于類似ResNet-34的架構并且基本上與dlib中實現的架構相對應。該網絡已經被訓練學習將人臉的特征映射到人臉描述符(descriptor ,具有128個值的特征矢量),這通常也被稱為人臉嵌入。

          現在回到我們最初的比較兩個人臉的問題:我們將使用每個提取的人臉圖像的人臉描述符并將它們與參考數據的人臉描述符進行比較。也就是說,我們可以計算兩個人臉描述符之間的歐氏距離,并根據閾值判斷兩個人臉是否相似(對于150 x 150大小的人臉圖像,0.6是一個很好的閾值)。使用歐幾里德距離的方法非常有效,當然,你也可以使用任何你選擇的分類器。以下gif通過歐幾里德距離將兩幅人臉圖像進行比較:

          學完了人臉識別的理論,我們可以開始編寫一個示例。

          編碼

          在這個簡短的例子中,我們將逐步了解如何在以下顯示多人的輸入圖像上進行人臉識別:

          包括腳本

          首先,從 dist/face-api.js或者dist/face-api.min.js的minifed版本中獲取最新的構建且包括腳本:

          <script src=“face-api.js”> </ script>
          

          鏈接:https://github.com/justadudewhohacks/face-api.js/tree/master/dist

          如果你使用npm:

          npm i face-api.js
          

          加載模型數據

          根據應用程序的要求,你可以專門加載所需的模型,但要運行完整的端到端示例,我們需要加載人臉檢測,人臉標識和人臉識別模型。模型文件在repo上可用,下方鏈接中找到。

          https://github.com/justadudewhohacks/face-api.js/tree/master/weights

          已經量化了模型權重,將模型文件大小減少了75%,以便使你的客戶端僅加載所需的最小數據。此外,模型權重被分割成最大4MB的塊,以允許瀏覽器緩存這些文件,使得它們只需加載一次。

          模型文件可以簡單地作為靜態資源(static asset)提供給你的Web應用程序,可以在其他地方托管它們,可以通過指定文件的路徑或url來加載它們。假設你在models目錄中提供它們并且資源在public/models下:

          const MODEL_URL='/models'
          await faceapi.loadModels(MODEL_URL)
          

          或者,如果你只想加載特定模型:

          const MODEL_URL='/models'
          await faceapi.loadFaceDetectionModel(MODEL_URL)
          await faceapi.loadFaceLandmarkModel(MODEL_URL)
          await faceapi.loadFaceRecognitionModel(MODEL_URL)
          

          從輸入圖像接收所有面孔的完整描述

          神經網絡接受HTML圖像,畫布或視頻元素或張量作為輸入。要使用score> minScore檢測輸入的人臉邊界框,我們只需聲明:

          const minConfidence=0.8
          const fullFaceDescriptions=await faceapi.allFaces(input, minConfidence)
          

          完整的人臉描述檢測結果(邊界框+分數)、人臉標志以及計算出的描述符。正如你所看到的,faceapi.allFaces在前面討論過的所有內容都適用于我們。但是,你也可以手動獲取人臉位置和標志。如果這是你的目標,github repo上有幾個示例。

          請注意,邊界框和標志位置是相對于原始圖像/媒體大小。如果顯示的圖像尺寸與原始圖像尺寸不一致,則可以調整它們的尺寸:

          const resized=fullFaceDescriptions.map(fd=> fd.forSize(width, height))
          

          我們可以通過將邊界框繪制到畫布中來可視化檢測結果:

          fullFaceDescription.forEach((fd, i)=> {
           faceapi.drawDetection(canvas, fd.detection, { withScore: true })
          })
          

          臉部可 以顯示如下:

          fullFaceDescription.forEach((fd, i)=> {
           faceapi.drawLandmarks(canvas, fd.landmarks, { drawLines: true })
          })
          

          通常,我所做的可視化工作是在img元素的頂部覆蓋一個絕對定位的畫布,其寬度和高度相同(參閱github示例以獲取更多信息)。

          人臉識別

          現在我們知道如何在給定輸入圖像的情況下檢索所有人臉的位置和描述符,即,我們將得到一些圖像,分別顯示每個人并計算他們的人臉描述符。這些描述符將成為我們的參考數據。

          假設我們有一些可用的示例圖像,我們首先從url獲取圖像,然后使用faceapi.bufferToImage從其數據緩沖區創建HTML圖像元素:

          // fetch images from url as blobs
          const blobs=await Promise.all(
           ['sheldon.png' 'raj.png', 'leonard.png', 'howard.png'].map(
           uri=> (await fetch(uri)).blob()
           )
          )
          // convert blobs (buffers) to HTMLImage elements
          const images=await Promise.all(blobs.map(
           blob=> await faceapi.bufferToImage(blob)
          ))
          

          接下來,對于每個圖像,我們定位主體的面部并計算人臉描述符,就像我們之前在輸入圖像時所做的那樣:

          const refDescriptions=await Promsie.all(images.map(
           img=> (await faceapi.allFaces(img))[0]
          ))
          const refDescriptors=refDescriptions.map(fd=> fd.descriptor)
          

          現在,我們要做的一切就是遍歷輸入圖像的人臉描述,并在參考數據中找到距離最近的描述符:

          const sortAsc=(a, b)=> a - b
          const labels=['sheldon', 'raj', 'leonard', 'howard']
          const results=fullFaceDescription.map((fd, i)=> {
           const bestMatch=refDescriptors.map(
           refDesc=> ({
           label: labels[i],
           distance: faceapi.euclideanDistance(fd.descriptor, refDesc)
           })
           ).sort(sortAsc)[0]
           
           return {
           detection: fd.detection,
           label: bestMatch.label,
           distance: bestMatch.distance
           }
          })
          

          如前所述,我們在此使用歐氏距離作為相似性度量,結果表明工作得很好。我們最終得到了在輸入圖像中檢測到的每個人臉的最佳匹配。

          最后,我們可以將邊界框與標簽一起繪制到畫布中以顯示結果:

          // 0.6 is a good distance threshold value to judge
          // whether the descriptors match or not
          const maxDistance=0.6
          results.forEach(result=> {
           faceapi.drawDetection(canvas, result.detection, { withScore: false })
           
           const text=`${result.distance < maxDistance ? result.className : 'unkown'} (${result.distance})`
           const { x, y, height: boxHeight }=detection.getBox()
           faceapi.drawText(
           canvas.getContext('2d'),
           x,
           y + boxHeight,
           text
           )
          })
          

          以上我希望你首先了解如何使用api。另外,我建議查看repo中的其他示例。

          來自:ATYUN


          主站蜘蛛池模板: 日韩精品无码久久一区二区三| 人妻互换精品一区二区| 日本免费一区二区三区最新| 国产一区二区电影在线观看| 国产一区二区三区在线免费观看| 伊人色综合视频一区二区三区 | 日韩精品中文字幕无码一区 | 激情内射日本一区二区三区| 最美女人体内射精一区二区| 人妻无码第一区二区三区| 福利一区二区在线| 性色av闺蜜一区二区三区| 国产精品一区不卡| 亚洲线精品一区二区三区影音先锋| 夜夜高潮夜夜爽夜夜爱爱一区| 视频一区二区中文字幕| 天堂资源中文最新版在线一区| 久久久国产精品亚洲一区| 亚洲线精品一区二区三区影音先锋| 精品理论片一区二区三区| 日本一区二三区好的精华液 | 久久国产精品一区免费下载| 久久精品免费一区二区| 日本精品啪啪一区二区三区| 亚洲精品伦理熟女国产一区二区| 久久久久人妻精品一区| 国产午夜精品一区二区三区 | 久久精品一区二区三区不卡| 中文人妻av高清一区二区| 一区二区三区无码视频免费福利 | 亚洲综合色一区二区三区| 99久久精品国产一区二区成人| 波多野结衣AV无码久久一区| 少妇人妻精品一区二区三区| 亚洲福利视频一区| 亚洲乱码av中文一区二区| 日韩高清一区二区三区不卡 | 国产精品视频无圣光一区| 中文字幕日韩欧美一区二区三区| 免费无码一区二区三区| 国产精品一区二区不卡|