整合營銷服務商

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

          免費咨詢熱線:

          face-api.js:在瀏覽器中進行人臉識別的JavaScript接口

          自IT Next,作者:Vincent Mühler,機器之心編譯,參與:Geek AI、張倩。

          本文將為大家介紹一個建立在「tensorflow.js」內核上的 javascript API——「face-api.js」,它實現了三種卷積神經網絡架構,用于完成人臉檢測、識別和特征點檢測任務,可以在瀏覽器中進行人臉識別。

          號外!號外!現在人們終于可以在瀏覽器中進行人臉識別了!本文將為大家介紹「face-api.js」,這是一個建立在「tensorflow.js」內核上的 javascript 模塊,它實現了三種卷積神經網絡(CNN)架構,用于完成人臉檢測、識別和特征點檢測任務。

          • face-api.js:https://github.com/justadudewhohacks/face-api.js
          • TensorFlow.js:https://github.com/tensorflow/tfjs-core

          像往常一樣,我們將查看一個簡單的代碼示例,這將使你能立即通過短短幾行代碼中的程序包開始了解這個 API。讓我們開始吧!

          我們已經有了「face-recognition.js」,現在又來了另一個同樣的程序包?

          如果你閱讀過本文作者另一篇關于「node.js」環境下進行人臉識別的文章《Node.js + face-recognition.js : Simple and Robust Face Recognition using Deep Learning》(Node.js + face-recognition.js:通過深度學習實現簡單而魯棒的人臉識別)(https://medium.com/@muehler.v/node-js-face-recognition-js-simple-and-robust-face-recognition-using-deep-learning-ea5ba8e852),你就會知道他在之前組裝過一個類似的程序包,例如「face-recgnition.js」,從而為「node.js」引入了人臉識別功能。

          起初,作者并沒有預見到 JavaScript 社區對與人臉識別程序包的需求程度如此之高。對許多人而言,「face-recognition.js」似乎是一個不錯的、能夠免費試用的開源選項,它可以替代由微軟或亞馬遜等公司提供的付費人臉識別服務。但是作者曾多次被問道:是否有可能在瀏覽器中運行完整的人臉識別的工作流水線?

          多虧了「tensorflow.js」,這種設想最終變為了現實!作者設法使用「tf.js

          」內核實現了部分類似的工具,它們能得到和「face-recognition.js」幾乎相同的結果,但是作者是在瀏覽器中完成的這項工作!而且最棒的是,這套工具不需要建立任何的外部依賴,使用它非常方便。并且這套工具還能通過 GPU 進行加速,相關操作可以使用 WebGL 運行。

          這足以讓我相信,JavaScript 社區需要這樣的一個為瀏覽器環境而編寫的程序包!可以設想一下你能通過它構建何種應用程序。

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

          如果想要盡快開始實戰部分,那么你可以跳過這一章,直接跳到代碼分析部分去。但是為了更好地理解「face-api.js」中為了實現人臉識別所使用的方法,我強烈建議你順著這個章節閱讀下去,因為我常常被人們問到這個問題。

          為簡單起見,我們實際想要實現的目標是在給定一張人臉的圖像時,識別出圖像中的人。為了實現這個目標,我們需要為每一個我們想要識別的人提供一張(或更多)他們的人臉圖像,并且給這些圖像打上人臉主人姓名的標簽作為參考數據。現在,我們將輸入圖像和參考數據進行對比,找到與輸入圖像最相似的參考圖像。如果有兩張圖像都與輸入足夠相似,那么我們輸出人名,否則輸出「unknown」(未知)。

          聽起來確實是個好主意!然而,這個方案仍然存在兩個問題。首先,如果我們有一張顯示了多人的圖像,并且我們需要識別出其中所有的人,將會怎樣呢?其次,我們需要建立一種相似度度量手段,用來比較兩張人臉圖像。

          人臉檢測

          我們可以從人臉檢測技術中找到第一個問題的答案。簡單地說,我們將首先定位輸入圖像中的所有人臉。「face-api.js」針對人臉檢測工作實現了一個 SSD(Single Shot Multibox Detector)算法,它本質上是一個基于 MobileNetV1 的卷積神經網絡(CNN),在網絡的頂層加入了一些人臉邊框預測層。

          該網絡將返回每張人臉的邊界框,并返回每個邊框相應的分數,即每個邊界框表示一張人臉的概率。這些分數被用于過濾邊界框,因為可能存在一張圖片并不包含任何一張人臉的情況。請注意,為了對邊界框進行檢索,即使圖像中僅僅只有一個人,也應該執行人臉檢測過程。

          人臉特征點檢測及人臉對齊

          在上文中,我們已經解決了第一個問題!然而,我想要指出的是,我們需要對齊邊界框,從而抽取出每個邊界框中的人臉居中的圖像,接著將其作為輸入傳給人臉識別網絡,因為這樣可以使人臉識別更加準確!

          為了實現這個目標,「face-api.js」實現了一個簡單的卷積神經網絡(CNN),它將返回給定圖像的 68 個人臉特征點:

          從特征點位置上看,邊界框可以將人臉居中。你可以從下圖中看到人臉檢測結果(左圖)與對齊后的人臉圖像(右圖)的對比:

          人臉識別

          現在,我們可以將提取出的對齊后的人臉圖像輸入到人臉識別網絡中,該網絡基于一個類似于 ResNet-34 的架構,基本上與 dlib(https://github.com/davisking/dlib/blob/master/examples/dnn_face_recognition_ex.cpp)中實現的架構一致。該網絡已經被訓練去學習出人臉特征到人臉描述符的映射(一個包含 128 個值的特征向量),這個過程通常也被稱為人臉嵌入。

          現在讓我們回到最初對比兩張人臉圖像的問題:我們將使用每張抽取出的人臉圖像的人臉描述符,并且將它們與參考數據的人臉描述符進行對比。更確切地說,我們可以計算兩個人臉描述符之間的歐氏距離,并根據閾值判斷兩張人臉圖像是否相似(對于 150*150 的圖像來說,0.6 是一個很好的閾值)。使用歐氏距離的效果驚人的好,當然,你也可以選用任何一種分類器。下面的 gif 動圖可視化了通過歐氏距離比較兩張人臉圖像的過程:

          至此,我們已經對人臉識別的理論有所了解。接下來讓我們開始編寫一個代碼示例。

          是時候開始編程了!

          在這個簡短的示例中,我們將看到如何一步步地運行人臉識別程序,識別出如下所示的輸入圖像中的多個人物:

          導入腳本

          首先,從 dist/face-api.js 獲得最新的版本(https://github.com/justadudewhohacks/face-api.js/tree/master/dist),或者從 dist/face-api.min.js 獲得縮減版,并且導入腳本:

          <script src="face-api.js"></script>

          如果你使用 npm 包管理工具,可以輸入如下指令:

          npm i face-api.js

          加載模型數據

          你可以根據應用程序的要求加載你需要的特定模型。但是如果要運行一個完整的端到端的示例,我們還需要加載人臉檢測、人臉特征點檢測和人臉識別模型。相關的模型文件可以在代碼倉庫中找到,鏈接如下:https://github.com/justadudewhohacks/face-api.js/tree/master/weights。

          其中,模型的權重已經被量化,文件大小相對于初始模型減小了 75%,使你的客戶端僅僅需要加載所需的最少的數據。此外,模型的權重被分到了最大為 4 MB 的數據塊中,使瀏覽器能夠緩存這些文件,這樣它們就只需要被加載一次。

          模型文件可以直接作為你的 web 應用中的靜態資源被使用,或者你可以將它們存放在另外的主機上,通過指定的路徑或文件的 url 鏈接來加載。假如你將它們與你在 public/models 文件夾下的資產共同存放在一個 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 圖像、畫布、視頻元素或張量(tensor)作為輸入。為了檢測出輸入圖像中分數(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 有了一個初步的認識。同時,我也建議你看看文中給出的代碼倉庫中的其它示例。好好地把這個程序包玩個痛快吧!

          ava進階架構師”,選擇右上角“置頂公眾號

          20大進階架構專題每日送達

          在設計接口時,有很多因素要考慮:

          • 接口的業務定位

          • 接口的安全性

          • 接口的可擴展性

          • 接口的穩定性

          • 接口的跨域性

          • 接口的協議規則

          • 接口的路徑規則

          • 接口單一原則

          • 接口過濾及接口組合

          本篇文章將簡要分析這些因素。

          一 規范性建議

          1.職責原則

          在設計接口時,必須明確接口的職責,即接口類型,接口應解決什么業務問題等

          2.單一性原則

          在明確接口職責的條件下,盡量做到接口單一,即一個接口只做一件事,而非兩件以上。

          很多非資深接口設計者,在設計接口時,總認為接口所做的事越多,越牛叉,這是非常嚴重的錯誤認識。

          3.協議規范

          在設計接口時,應明確接口協議,是采用HTTP協議,HTTPS協議還是FTP協議,要根據具體情況來定。

          (1)FTP協議(File Transfer Protocol,簡稱FTP),是一套標準的文件傳輸協議,用于傳輸文件,如.txt,.csv等,一般文件傳輸,采用FTP協議

          (2)HTTP協議,適用一般對安全性要求比較低或沒要求的業務情景

          (3)HTTPS=HTTP+SSL,適用于對安全性要求較高的業務情景

          4.路徑規則

          由于api獲取的是一種資源,所以網址中盡量為名詞,而非動詞

          /api/v1.0/Product/2019

          /api/v1.0/Users/2019

          5.http請求方式

          接口基本訪問協議:get(獲取),post(新增),put(修改)和delete(刪除)

          get /users:列出所有用戶

          get /users/id:根據id獲取用戶

          post /user:新增用戶

          put /user/id:根據用戶id更新用戶

          delete /user/id:根據用戶id刪除用戶

          6.域名

          一般地,域名分為主域名和專有域名,主域名適合api長期不變或變化較少的業務,專有域名是解決具體的專有業務的

          以百度舉例:

          (1)主域名:www.baidu.com

          (2)產品服務類

          百度文庫:https://wenku.baidu.com/

          百度知道:https://zhidao.baidu.com/

          百度資訊:https://zhidao.baidu.com/

          (3)市場活動類

          百度公益:http://gongyi.baidu.com

          百度logo:http://logo.baidu.com/

          百度世界:https://baiduworld.baidu.com

          7.跨域考慮

          在明確域名的情況下,一定要考慮接口是否跨域,以及跨域應采用的技術手段等

          8.api版本

          對于接口的url,應加版本號http://api.demo.com/v7d7v7vh/,如 ,其中d表示版本號,如v1.0,v2.0

          例子:獲取產品號為2019,版本號為v1.0的版本號的產品信息

          /api/v1.0/Pruducts/2019

          9.適度過濾信息

          當記錄數比較多時(如 SELECT * FROM TBName),應適當添加一些條件對數據進行過濾,如TOP,分頁,分組,排序和WHERE條件等

          下面是一些常見的參數。

          ?limit=100:返回100條數據

          ?offset=101:從第101條數據開始返回

          ?page=10:指第10頁

          per_page=100:每頁100條數據

          ?sortby=name:排序字段

          ?order=desc:降序

          ?group=groupName:分組

          ?producy_type=1:篩選條件

          10.返回數據格式

          返回數據格式,一般包括三個字段:

          (1)失敗情況(狀態碼、錯誤碼和錯誤描述)

          {

          “status”:0,//狀態碼 0-表示失敗,1-表示成功

          “error_code”:“2003”,//錯誤碼,一般在設計時定義

          “error_des”:“身份驗證失敗”//錯誤描述,一般在設計時定義

          }

          (2)成功情況(標識id,數據對象,狀態碼)

          {

          “sid“:“sh20190111”,//token id

          “users”:{

          “id”:“al201901111341”,//用戶id

          “name”:“Alan_beijing”,//用戶名

          “addr”:“用戶地址”

          },

          “status”:1//狀態碼 0-表示失敗,1-表示成功

          }

          11.安全性原則

          接口暴露的考慮,接口并發量的考慮,接口防攻擊的考慮,接口跨域的考慮等

          12.可擴展性原則

          在設計接口時,充分考慮接口的可擴展性。

          13.定義api界限

          任何api,從權限上,可歸結為匿名api和非匿名api,前者不需要驗證,后者需要驗證

          14.定義api返回碼

          在api設計時,要定好api返回碼,如

          • 1 --授權過期

          • 404--未找到資源

          • 500--內部服務器錯誤

          • 600--賬號被鎖

          二 反規范性建議

          存在這樣一種業務場景:某個接口需要返回多個api接口組合的結果 ,在類似的業務場景下,所設計的接口,具有一定的反規范性。

          1.Request

          data:[
          {url:'api1',type:'get',data:{...}},
          {url:'api2',type:'get',data:{...}},
          ]

          2.Responce

          {
          status:0,
          msg:'',
          data:[
          {status:1,msg:'',data:[]},
          {status:1,msg:'',data:{}}
          ]
          }

          三 實例

          假設存在這樣一個一個業務:一個ERP系統,需要提供兩個接口,一個是用戶訪問接口(需要驗證),另一個是用戶注冊接口(不需要驗證)。

          根據本篇文章一,二部分的建議,我們來設計滿足該業務需求的接口

          (一)定義統一參數

          1.定義統一輸入參數

          2.定義統一輸出參數

          3.定義統一錯誤碼

          (二)定義接口授權類別

          如下為定義接口授權類別

          (三)用戶接口

          1.用戶注冊

          2.Request

          3.Responce

          4.code示例

          Request:
          {
          "mobile":13636595499,
          "verify_code":"987654",
          "pwd":"123456"
          }

          Responce:
          (1)error
          {
          "status":0,
          "error_code":1001,
          "error_desc":"手機驗證碼已失效"
          }
          (2)succed
          {
          "sid":"sh201901141529",
          "uid":1,
          "status":1
          }

          (四)用戶登錄

          1.登錄接口概述

          2.Request

          3.Responce

          4.Code

          Responce:
          1.error
          {
          "status":0,
          "error_code":1002,
          "error_desc":"密碼錯誤"
          }
          2.succeed
          {
          "sid":"sh201901141529",
          "user":{
          "id":1,
          "username":"",
          age:0,
          gender:0
          },
          "status":1
          }

          作者:Alan

          本文來源:

          https://www.cnblogs.com/wangjiming/p/10256546.html

          ———— e n d ————

          微服務、高并發、JVM調優、面試專欄等20 大進階架構師專題請關注公眾號【Java進階架構師】后在菜單欄查看。

          看到這里,說明你喜歡本文

          家好,我是皮皮。

          前言

          對于前端來說,HTML 都是最基礎的內容。

          今天,我們來了解一下 HTML 和網頁有什么關系,以及與 DOM 有什么不同。通過本講內容,你將掌握瀏覽器是怎么處理 HTML 內容的,以及在這個過程中我們可以進行怎樣的處理來提升網頁的性能,從而提升用戶的體驗。


          一、瀏覽器頁面加載過程

          不知你是否有過這樣的體驗:當打開某個瀏覽器的時候,發現一直在轉圈,或者等了好長時間才打開頁面……

          此時的你,會選擇關掉頁面還是耐心等待呢?

          這一現象,除了網絡不穩定、網速過慢等原因,大多數都是由于頁面設計不合理導致加載時間過長導致的。

          我們都知道,頁面是用 HTML/CSS/JavaScript 來編寫的。

          • HTML 的職責在于告知瀏覽器如何組織頁面,以及搭建頁面的基本結構;
          • CSS 用來裝飾 HTML,讓我們的頁面更好看;
          • JavaScript 則可以豐富頁面功能,使靜態頁面動起來。

          HTML由一系列的元素組成,通常稱為HTML元素。HTML 元素通常被用來定義一個網頁結構,基本上所有網頁都是這樣的 HTML 結構:

          <html>
              <head></head>
              <body></body>
          </html>

          其中:

          • html元素是頁面的根元素,它描述完整的網頁;
          • head元素包含了我們想包含在 HTML 頁面中,但不希望顯示在網頁里的內容;
          • body元素包含了我們訪問頁面時所有顯示在頁面上的內容,是用戶最終能看到的內容;


          HTML 中的元素特別多,其中還包括可用于 Web Components 的自定義元素。

          前面我們提到頁面 HTML 結構不合理可能會導致頁面響應慢,這個過程很多時候體現在<script><style>元素的設計上,它們會影響頁面加載過程中對 Javascript 和 CSS 代碼的處理。

          因此,如果想要提升頁面的加載速度,就需要了解瀏覽器頁面的加載過程是怎樣的,從根本上來解決問題。

          瀏覽器在加載頁面的時候會用到 GUI 渲染線程和 JavaScript 引擎線程(更詳細的瀏覽器加載和渲染機制將在第 7 講中介紹)。其中,GUI 渲染線程負責渲染瀏覽器界面 HTML 元素,JavaScript 引擎線程主要負責處理 JavaScript 腳本程序。

          由于 JavaScript 在執行過程中還可能會改動界面結構和樣式,因此它們之間被設計為互斥的關系。也就是說,當 JavaScript 引擎執行時,GUI 線程會被掛起。

          以網易云課堂官網為例,我們來看看網頁加載流程。

          (1)當我們打開官網的時候,瀏覽器會從服務器中獲取到 HTML 內容。

          (2)瀏覽器獲取到 HTML 內容后,就開始從上到下解析 HTML 的元素。

          (3)<head>元素內容會先被解析,此時瀏覽器還沒開始渲染頁面。

          我們看到<head>元素里有用于描述頁面元數據的<meta>元素,還有一些<link>元素涉及外部資源(如圖片、CSS 樣式等),此時瀏覽器會去獲取這些外部資源。除此之外,我們還能看到<head>元素中還包含著不少的<script>元素,這些<script>元素通過src屬性指向外部資源。

          (4)當瀏覽器解析到這里時(步驟 3),會暫停解析并下載 JavaScript 腳本。

          (5)當 JavaScript 腳本下載完成后,瀏覽器的控制權轉交給 JavaScript 引擎。當腳本執行完成后,控制權會交回給渲染引擎,渲染引擎繼續往下解析 HTML 頁面。

          (6)此時<body>元素內容開始被解析,瀏覽器開始渲染頁面。

          在這個過程中,我們看到<head>中放置的<script>元素會阻塞頁面的渲染過程:把 JavaScript 放在<head>里,意味著必須把所有 JavaScript 代碼都下載、解析和解釋完成后,才能開始渲染頁面。

          到這里,我們就明白了:如果外部腳本加載時間很長(比如一直無法完成下載),就會造成網頁長時間失去響應,瀏覽器就會呈現“假死”狀態,用戶體驗會變得很糟糕。

          因此,對于對性能要求較高、需要快速將內容呈現給用戶的網頁,常常會將 JavaScript 腳本放在<body>的最后面。這樣可以避免資源阻塞,頁面得以迅速展示。我們還可以使用defer/async/preload等屬性來標記<script>標簽,來控制 JavaScript 的加載順序。

          百度首頁

          三、DOM 解析

          對于百度這樣的搜索引擎來說,必須要在最短的時間內提供到可用的服務給用戶,其中就包括搜索框的顯示及可交互,除此之外的內容優先級會相對較低。

          瀏覽器在渲染頁面的過程需要解析 HTML、CSS 以得到 DOM 樹和 CSS 規則樹,它們結合后才生成最終的渲染樹并渲染。因此,我們還常常將 CSS 放在<head>里,可用來避免瀏覽器渲染的重復計算。


          二、HTML 與 DOM 有什么不同

          我們知道<p>是 HTML 元素,但又常常將<p>這樣一個元素稱為 DOM 節點,那么 HTML 和 DOM 到底有什么不一樣呢?

          根據 MDN 官方描述:文檔對象模型(DOM)是 HTML 和 XML 文檔的編程接口。

          也就是說,DOM 是用來操作和描述 HTML 文檔的接口。如果說瀏覽器用 HTML 來描述網頁的結構并渲染,那么使用 DOM 則可以獲取網頁的結構并進行操作。一般來說,我們使用 JavaScript 來操作 DOM 接口,從而實現頁面的動態變化,以及用戶的交互操作。

          在開發過程中,常常用對象的方式來描述某一類事物,用特定的結構集合來描述某些事物的集合。DOM 也一樣,它將 HTML 文檔解析成一個由 DOM 節點以及包含屬性和方法的相關對象組成的結構集合。


          三、DOM 解析

          我們常見的 HTML 元素,在瀏覽器中會被解析成節點。比如下面這樣的 HTML 內容:

          <html>
              <head>
                  <title>標題</title>
              </head>
              <body>
                  <a href='xx.com'>我的超鏈接</a>
                  <h1>頁面第一標題</h1>
              </body>
          </html>

          打開控制臺 Elements 面板,可以看到這樣的 HTML 結構,如下圖所示:

          在瀏覽器中,上面的 HTML 會被解析成這樣的 DOM 樹,如下圖所示:


          我們都知道,對于樹狀結構來說,常常使用parent/child/sibling等方式來描述各個節點之間的關系,對于 DOM 樹也不例外。

          舉個例子,我們常常會對頁面功能進行抽象,并封裝成組件。但不管怎么進行整理,頁面最終依然是基于 DOM 的樹狀結構,因此組件也是呈樹狀結構,組件間的關系也同樣可以使用parent/child/sibling這樣的方式來描述。同時,現在大多數應用程序同樣以root為根節點展開,我們進行狀態管理、數據管理也常常會呈現出樹狀結構。


          四、事件委托

          我們知道,瀏覽器中各個元素從頁面中接收事件的順序包括事件捕獲階段、目標階段、事件冒泡階段。其中,基于事件冒泡機制,我們可以實現將子元素的事件委托給父級元素來進行處理,這便是事件委托。

          如果我們在每個元素上都進行監聽的話,則需要綁定三個事件;(假設頁面上有a,b,c三個兄弟節點)

          function clickEventFunction(e) {
            console.log(e.target === this); // logs `true`
            // 這里可以用 this 獲取當前元素
          }
          // 元素a,b,c綁定
          element2.addEventListener("click", clickEventFunction, false);
          element5.addEventListener("click", clickEventFunction, false);
          element8.addEventListener("click", clickEventFunction, false);

          使用事件委托,可以通過將事件添加到它們的父節點,而將事件委托給父節點來觸發處理函數:

          function clickEventFunction(event) {
            console.log(e.target === this); // logs `false`
            // 獲取被點擊的元素
            const eventTarget = event.target;
            // 檢查源元素`event.target`是否符合預期
            // 此處控制廣告面板的展示內容
          }
          // 元素1綁定
          element1.addEventListener("click", clickEventFunction, false);

          這樣能解決什么問題呢?

          • 綁定子元素會綁定很多次的事件,而綁定父元素只需要一次綁定。
          • 將事件委托給父節點,這樣我們對子元素的增加和刪除、移動等,都不需要重新進行事件綁定。

          常見的使用方式主要是上述這種列表結構,每個選項都可以進行編輯、刪除、添加標簽等功能,而把事件委托給父元素,不管我們新增、刪除、更新選項,都不需要手動去綁定和移除事件。

          如果在列表數量內容較大的時候,對成千上萬節點進行事件監聽,也是不小的性能消耗。使用事件委托的方式,我們可以大量減少瀏覽器對元素的監聽,也是在前端性能優化中比較簡單和基礎的一個做法。

          注意:

          1. 如果我們直接在document.body上進行事件委托,可能會帶來額外的問題;
          2. 由于瀏覽器在進行頁面渲染的時候會有合成的步驟,合成的過程會先將頁面分成不同的合成層,而用戶與瀏覽器進行交互的時候需要接收事件。此時,瀏覽器會將頁面上具有事件處理程序的區域進行標記,被標記的區域會與主線程進行通信。
          3. 如果我們document.body上被綁定了事件,這時候整個頁面都會被標記;
          4. 即使我們的頁面不關心某些部分的用戶交互,合成器線程也必須與主線程進行通信,并在每次事件發生時進行等待。這種情況,我們可以使用passive: true選項來解決


          五、總結

          我們了解了 HTML 的作用,以及它是如何影響瀏覽器中頁面的加載過程的,同時還介紹了使用 DOM 接口來控制 HTML 的展示和功能邏輯。我們了解了DOM解析事件委托等相關概念。


          主站蜘蛛池模板: 国产亚洲一区二区三区在线| 国产成人精品一区二三区熟女 | 久久久久人妻精品一区二区三区| 中文字幕无码不卡一区二区三区| 冲田杏梨高清无一区二区| 国产麻豆精品一区二区三区v视界| 亚洲国产AV无码一区二区三区| 亚洲无线码在线一区观看| 日韩三级一区二区| 国产福利91精品一区二区三区| 国产午夜精品免费一区二区三区| 国产萌白酱在线一区二区| 国精产品999一区二区三区有限| 亚洲AV福利天堂一区二区三| 少妇人妻偷人精品一区二区| 一区二区三区观看| 精品无码中出一区二区| 国产SUV精品一区二区四 | tom影院亚洲国产一区二区| 中文字幕亚洲乱码熟女一区二区| 婷婷国产成人精品一区二| 国产女人乱人伦精品一区二区| 国产福利日本一区二区三区| 女女同性一区二区三区四区| 一区二区亚洲精品精华液| 亚洲愉拍一区二区三区| 日韩一区二区在线视频| 亚洲日本久久一区二区va| 亚洲日韩国产一区二区三区在线 | 一区二区三区在线免费观看视频| 精品一区二区三区在线视频观看| 国产福利一区二区精品秒拍| 欧美日韩精品一区二区在线观看| 无码人妻精品一区二区三区久久久| 亚洲精品日韩一区二区小说| 正在播放国产一区| 中文字幕在线观看一区| 精品福利一区二区三| 麻豆天美国产一区在线播放| 亚洲一区在线视频观看| 亚洲欧美国产国产综合一区 |