整合營銷服務(wù)商

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

          免費咨詢熱線:

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

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

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

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

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

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

          我們已經(jīng)有了「face-recognition.js」,現(xiàn)在又來了另一個同樣的程序包?

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

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

          多虧了「tensorflow.js」,這種設(shè)想最終變?yōu)榱爽F(xiàn)實!作者設(shè)法使用「tf.js

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

          這足以讓我相信,JavaScript 社區(qū)需要這樣的一個為瀏覽器環(huán)境而編寫的程序包!可以設(shè)想一下你能通過它構(gòu)建何種應(yīng)用程序。

          如何利用深度學(xué)習(xí)解決人臉識別問題

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

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

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

          人臉檢測

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

          該網(wǎng)絡(luò)將返回每張人臉的邊界框,并返回每個邊框相應(yīng)的分?jǐn)?shù),即每個邊界框表示一張人臉的概率。這些分?jǐn)?shù)被用于過濾邊界框,因為可能存在一張圖片并不包含任何一張人臉的情況。請注意,為了對邊界框進行檢索,即使圖像中僅僅只有一個人,也應(yīng)該執(zhí)行人臉檢測過程。

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

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

          為了實現(xiàn)這個目標(biāo),「face-api.js」實現(xiàn)了一個簡單的卷積神經(jīng)網(wǎng)絡(luò)(CNN),它將返回給定圖像的 68 個人臉特征點:

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

          人臉識別

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

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

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

          是時候開始編程了!

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

          導(dǎo)入腳本

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

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

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

          npm i face-api.js

          加載模型數(shù)據(jù)

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

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

          模型文件可以直接作為你的 web 應(yīng)用中的靜態(tài)資源被使用,或者你可以將它們存放在另外的主機上,通過指定的路徑或文件的 url 鏈接來加載。假如你將它們與你在 public/models 文件夾下的資產(chǎn)共同存放在一個 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)

          從輸入圖像中得到對所有人臉的完整描述

          該神經(jīng)網(wǎng)絡(luò)可以接收 HTML 圖像、畫布、視頻元素或張量(tensor)作為輸入。為了檢測出輸入圖像中分?jǐn)?shù)(score)大于最小閾值(minScore)的人臉邊界框,我們可以使用下面的簡單操作:

          const minConfidence = 0.8

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

          一個完整的人臉描述符包含了檢測結(jié)果(邊界框+分?jǐn)?shù)),人臉特征點以及計算出的描述符。正如你所看到的,「faceapi.allFaces」在底層完成了本文前面的章節(jié)所討論的所有工作。然而,你也可以手動地獲取人臉定位和特征點。如果這是你的目的,你可以參考 github repo 中的幾個示例。

          請注意,邊界框和特征點的位置與原始圖像/媒體文件的尺寸有關(guān)。當(dāng)顯示出的圖像尺寸與原始圖像的尺寸不相符時,你可以簡單地通過下面的方法重新調(diào)整它們的大小:

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

          我們可以通過將邊界框在畫布上繪制出來對檢測結(jié)果進行可視化:

          fullFaceDescription.forEach((fd, i) => {

          faceapi.drawDetection(canvas, fd.detection, { withScore: true })

          })

          可以通過下面的方法將人臉特征點顯示出來:

          fullFaceDescription.forEach((fd, i) => {

          faceapi.drawLandmarks(canvas, fd.landmarks, { drawLines: true })

          })

          通常,我會在 img 元素的頂層覆蓋一個具有相同寬度和高度的絕對定位的畫布(想獲取更多信息,請參閱 github 上的示例)。

          人臉識別

          當(dāng)我們知道了如何得到給定的圖像中所有人臉的位置和描述符后,我們將得到一些每張圖片顯示一個人的圖像,并且計算出它們的人臉描述符。這些描述符將作為我們的參考數(shù)據(jù)。

          假設(shè)我們有一些可以用的示例圖片,我們首先從一個 url 鏈接處獲取圖片,然后使用「faceapi.bufferToImage」從它們的數(shù)據(jù)緩存中創(chuàng)建 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)

          現(xiàn)在,我們還需要做的就是遍歷我們輸入圖像的人臉描述符,并且找到參考數(shù)據(jù)中與輸入圖像距離最小的描述符:

          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

          }

          })

          正如前面提到的,我們在這里使用歐氏距離作為一種相似度度量,這樣做的效果非常好。我們在輸入圖像中檢測出的每一張人臉都是匹配程度最高的。

          最后,我們可以將邊界框和它們的標(biāo)簽一起繪制在畫布上,顯示檢測結(jié)果:

          // 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 有了一個初步的認(rèn)識。同時,我也建議你看看文中給出的代碼倉庫中的其它示例。好好地把這個程序包玩?zhèn)€痛快吧!

          節(jié)內(nèi)容是"VBA信息獲取與處理"教程中第八個專題"VBA與HTML文檔"的第五節(jié)。,希望想掌握這方面知識的朋友能參考我的教程學(xué)習(xí)。

          第五節(jié) HTML文檔的訪問端口DOM (Document Object Model)

          大家好,我們繼續(xù)對HTML文檔進行學(xué)習(xí),在前幾節(jié)中我們認(rèn)識了網(wǎng)頁文檔上面的各種元素,那么我們又該怎么訪問他們呢,是如何實現(xiàn)呢?其實,HTML文檔,提供了訪問其元素的端口,這就是HTML DOM,其定義了一套標(biāo)準(zhǔn)的針對 HTML 文檔的對象訪問或操作的機制。HTML DOM 定義了所有 HTML 元素的對象和屬性,以及訪問它們的方法。換言之,HTML DOM 是關(guān)于如何獲取、修改、添加或刪除 HTML 元素的標(biāo)準(zhǔn)。

          1 HTML DOM (Document Object Model) 對象集合

          每個載入瀏覽器的 HTML 文檔都會成為 Document 對象。Document 對象使我們可以從腳本中對 HTML 頁面中的所有元素進行訪問。Document 對象是 HTML 文檔的根節(jié)點。

          特別注意:Document 對象是 Window 對象的一部分,可通過 window.document 屬性對其進行訪問。

          集合 描述

          all[] 提供對文檔中所有 HTML 元素的訪問。

          anchors[] 返回對文檔中所有 Anchor 對象的引用。

          applets 返回對文檔中所有 Applet 對象的引用。

          forms[] 返回對文檔中所有 Form 對象引用。

          images[] 返回對文檔中所有 Image 對象引用。

          links[] 返回對文檔中所有 Area 和 Link 對象引用。


          VBA直接支持調(diào)用的對象有限,如下:

          對象 描述

          Document 代表整個 HTML 文檔,可被用來訪問頁面中的所有元素

          Anchors 代表 <a> 元素集合

          Body 代表 <body> 元素

          Forms 代表 <form> 元素集合

          Frames 代表 <frame> 元素或<iframe> 元素集合

          Images 代表 <img> 元素集合

          Links 代表 <link> 元素集合

          Options 代表 <option> 元素集合(select元素里面可以直接使用)

          Cells 代表 <td> 元素集合(table元素里面可以直接使用)

          Rows 代表 <tr> 元素集合(table元素里面可以直接使用)

          All 對象集合,提供對文檔中所有 HTML 元素的訪問。


          上述VBA支持直接調(diào)用對象的元素,調(diào)用起來就非常方便了,可以用序號調(diào)用,例如forms(0),也可以按名稱/ID調(diào)用,例如forms("名稱/ID")。其他沒法直接調(diào)用對象的元素,可以間接從通用all對象集合中去調(diào)用該元素或該元素對應(yīng)的dom對象。

          2 HTML DOM 文檔的節(jié)點及文檔樹

          在HTML DOM中,所有事物都是節(jié)點。DOM 是被視為節(jié)點樹的 HTML文檔的每一部分都是節(jié)點,整個文檔被定義為一個文檔節(jié)點,每個標(biāo)簽是一個元素節(jié)點,包含在元素中的文本是文本節(jié)點,每一個元素的屬性是一個屬性節(jié)點。如下:

          1)整個文檔是一個文檔節(jié)點

          2)每個 HTML 元素是元素節(jié)點

          3)HTML 元素內(nèi)的文本是文本節(jié)點

          4)每個 HTML 屬性是屬性節(jié)點

          5)注釋是注釋節(jié)點


          HTML DOM 將 HTML 文檔視作樹結(jié)構(gòu)。這種結(jié)構(gòu)被稱為節(jié)點樹,這時一個非常形象的比喻,根節(jié)點就是html,主干就是body等,枝干就是body內(nèi)含的各種元素節(jié)點,葉子和花就是元素節(jié)點關(guān)聯(lián)的元素的屬性(屬性節(jié)點)或內(nèi)含文本(文本節(jié)點)。


          通過 HTML DOM,樹中的所有節(jié)點均可通過 JavaScript 進行訪問。所有 HTML 元素(節(jié)點)均可被修改,也可以創(chuàng)建或刪除節(jié)點。節(jié)點樹中的節(jié)點彼此擁有層級關(guān)系。父(parent)、子(child)和同胞(sibling)等術(shù)語用于描述這些關(guān)系。父節(jié)點擁有子節(jié)點。同級的子節(jié)點被稱為同胞(兄弟或姐妹)。

          網(wǎng)頁文檔,可以說是由元素組合而成的,也可以說是由節(jié)點連接而成的樹構(gòu)造的。節(jié)點是文檔樹結(jié)構(gòu)中特有的名詞,元素是節(jié)點,但是節(jié)點不一定是元素,節(jié)點還有文檔節(jié)點、文本節(jié)點、屬性節(jié)點等;元素是元素節(jié)點的擴展,元素可以擁有屬性還有文本。初學(xué)者不是很必要糾結(jié)元素和節(jié)點的區(qū)別,這個需要甚至數(shù)據(jù)結(jié)構(gòu)、類和繼承等知識。可大致理解為,同一個html dom對象,提供的兩個不同接口。如下的程序,你會發(fā)現(xiàn),他既有支持node(節(jié)點)的相關(guān)屬性和方法,也提供了element(元素)的相關(guān)屬性和方法。


          Sub MYNZ()

          Dim ie, dmt, bd

          Set ie = CreateObject("InternetExplorer.Application") '創(chuàng)建一個IE對象

          With ie

          .Visible = True '顯示它

          .navigate "http://www.baidu.com" '加載某個頁面

          Do Until .ReadyState = 4 '等待頁面加載完畢

          DoEvents

          Loop

          Set dmt = .document '將IE瀏覽器加載的頁面文檔,賦予dmt變量

          Set bd = dmt.body '將文檔的body賦予bd變量

          End With

          End Sub


          代碼截圖:

          按F8鍵運行截圖:

          3 HTML DOM 文檔的對象屬性

          屬性是節(jié)點(HTML 元素)的值,您能夠獲取或設(shè)置。所有 HTML 元素被定義為對象,而編程接口則是對象方法和對象屬性。我們看看下面一些常見的屬性:


          1)innerHTML 屬性 對象內(nèi)部的HTML代碼,是獲取元素內(nèi)容的最簡單方法。innerHTML 屬性對于獲取或替換 HTML 元素的內(nèi)容很有用。

          下面的代碼獲取 id="intro" 的 <p> 元素的 innerHTML:

          <script>

          var txt=document.getElementById("intro").innerHTML;

          document.write(txt);


          2)OuterHtml:對象的HTML代碼,包括對象本身的HTML標(biāo)簽


          3)innerText:對象內(nèi)部的文本


          4)OuterText:對象的文本,包括對象本身的文本


          5)nodeName 屬性 nodeName 屬性規(guī)定節(jié)點的名稱。

          nodeName 是只讀的

          元素節(jié)點的 nodeName 與標(biāo)簽名相同

          屬性節(jié)點的 nodeName 與屬性名相同

          文本節(jié)點的 nodeName 始終是 #text

          文檔節(jié)點的 nodeName 始終是 #document


          注釋:nodeName 始終包含 HTML 元素的大寫字母標(biāo)簽名。


          6)nodeValue 屬性 nodeValue 屬性規(guī)定節(jié)點的值。

          元素節(jié)點的 nodeValue 是 undefined 或 null

          文本節(jié)點的 nodeValue 是文本本身

          屬性節(jié)點的 nodeValue 是屬性值


          7)nodeType 屬性 nodeType 屬性返回節(jié)點的類型。nodeType 是只讀的。

          比較重要的節(jié)點類型有:


          元素類型 NodeType

          元素 1

          屬性 2

          文本 3

          注釋 8

          文檔 9


          以上只是給出了屬性值,大家在使用的時候要注意。


          本節(jié)知識點回向:


          什么是HTML DOM文檔的屬性,常用的屬性有哪些。


          本節(jié)參考文件:008工作表.xlsm

          積木編程的思路內(nèi)涵:

          在我的系列書籍中一直在強調(diào)"搭積木"的編程思路,這也是學(xué)習(xí)利用VBA的主要方法,特別是職場人員,更是要采用這種方案。其主要的內(nèi)涵:

          1 代碼不要自己全部的錄入。你要做的是把積木放在合適的位置然后去修正代碼,一定要拷貝,從你的積木庫中去拷貝,然后修正代碼,把時間利用到高效的思考上。

          2 建立自己的"積木庫"。平時在學(xué)習(xí)過程中,把自己認(rèn)為有用的代碼放在一起,多積累,在用到的時候,可以隨時拿來。你的積木庫資料越多,你做程序的思路就會越廣。

          VBA的應(yīng)用界定

          VBA是利用Office實現(xiàn)個人小型辦公自動化的有效手段(工具)。這是我對VBA的應(yīng)用界定。在取代OFFICE新的辦公軟件沒有到來之前,誰能在數(shù)據(jù)處理方面做到極致,誰就是王者。其中登峰至極的技能非VBA莫屬!

          我記得20年前自己初學(xué)VBA時,那時的資料甚少,只能看源碼自己琢磨,真的很難。20年過去了,為了不讓學(xué)習(xí)VBA的朋友重復(fù)我之前的經(jīng)歷,我根據(jù)自己多年VBA實際利用經(jīng)驗,推出了六部VBA專門教程:

          第一套:VBA代碼解決方案 是VBA中各個知識點的講解,教程共147講,覆蓋絕大多數(shù)的VBA知識點,初學(xué)必備;

          第二套:VBA數(shù)據(jù)庫解決方案 數(shù)據(jù)庫是數(shù)據(jù)處理的專業(yè)利器,教程中詳細介紹了利用ADO連接ACCDB和EXCEL的方法和實例操作,適合中級人員的學(xué)習(xí)。

          第三套:VBA數(shù)組與字典解決方案 數(shù)組和字典是VBA的精華,字典是VBA代碼水平提高的有效手段,值得深入的學(xué)習(xí),是初級及中級人員代碼精進的手段。

          第四套:VBA代碼解決方案之視頻 是專門面向初學(xué)者的視頻講解,可以快速入門,更快的掌握這門技能。這套教程是第一套教程的視頻講解,聽元音更易接受。

          第五套:VBA中類的解讀和利用 這是一部高級教程,講解類的虛無與肉身的度化,類的利用雖然較少,但仔細的學(xué)習(xí)可以促進自己VBA理論的提高。這套教程的領(lǐng)會主要是讀者的領(lǐng)悟了,領(lǐng)悟一種佛學(xué)的哲理。

          第六套教程:《VBA信息獲取與處理》是一部高級教程,涉及范圍更廣,實用性更強,面向中高級人員。教程共二十個專題,包括:跨應(yīng)用程序信息獲得、隨機信息的利用、電子郵件的發(fā)送、VBA互聯(lián)網(wǎng)數(shù)據(jù)抓取、VBA延時操作,剪切板應(yīng)用、Split函數(shù)擴展、工作表信息與其他應(yīng)用交互,F(xiàn)SO對象的利用、工作表及文件夾信息的獲取、圖形信息的獲取以及定制工作表信息函數(shù)等等內(nèi)容。

          大家可以根據(jù)以上資料1→3→2→6→5或者是4→3→2→6→5的順序逐漸深入的逐漸學(xué)習(xí)。教程提供講解的同時提供了大量的積木,如需要可以WeChat: NZ9668


          學(xué)習(xí)VBA是個過程,也需要經(jīng)歷一種枯燥的感覺

          如太白詩云:眾鳥高飛盡,孤云獨去閑。相看兩不厭,只有敬亭山。學(xué)習(xí)的過程也是修心的過程,修一個平靜的心。在代碼的世界中,心平靜了,心情好了,身體自然而然就好。心靜則正,內(nèi)心里沒有那么多邪知邪見,也就沒有那么多妄想。利人就是利己。這些教程也是為幫助大家起航,助上我自己之力,我的上述教程是我多的經(jīng)驗的傳遞,

          "水善利萬物而不爭",綿綿密密,微則無聲,巨則洶涌。學(xué)習(xí)亦如此,知道什么是自己所需要的,不要蜷縮在一小塊自認(rèn)為天堂的世界里,待到暮年時再去做自欺欺人的言論。要努力提高自己,用一顆充滿生機的心靈,把握現(xiàn)在,這才是進取。越是有意義的事情,困難會越多。愿力決定始終,智慧決定成敗。不管遇到什么,都是風(fēng)景。看淡紛爭,看輕得失。茶,滿也好,少也好,不要計較;濃也好,淡也好,其中自有值得品的味道。去感悟真實的時間,靜下心,多學(xué)習(xí),積累福報。而不是天天混日子,也不是天天熬日子。在后疫情更加嚴(yán)峻的存量殘殺世界中,為自己的生存進行知識的儲備,特別是新知識的儲備。學(xué)習(xí)時微而無聲,利用時則巨則洶涌。

          每一分收獲都是成長的記錄,怎無憑,正是這種執(zhí)著,成就了朝霞的燦爛。最后將一闕詞送給致力于VBA學(xué)習(xí)的朋友,讓大家感受一下學(xué)習(xí)過程的枯燥與執(zhí)著:


          浮云掠過,暗語無聲,

          唯有清風(fēng),驚了夢中啼鶯。

          望星,疏移北斗,

          奈將往事雁同行。

          阡陌人,昏燈明暗,

          忍顧長亭。

          多少VBA人,

          暗夜中,悄聲尋夢,盼卻天明。

          怎無憑!


          回向?qū)W習(xí)利用VBA的歷歷往事,不勝感慨,謹(jǐn)以這些文字給大家,分享我多年工作實際經(jīng)驗的成果,隨喜這些有用的東西,給確實需要利用VBA的同路人。


          分享成果,隨喜正能量

          家好,我是皮皮。

          前言

          對于前端來說,HTML 都是最基礎(chǔ)的內(nèi)容。

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


          一、瀏覽器頁面加載過程

          不知你是否有過這樣的體驗:當(dāng)打開某個瀏覽器的時候,發(fā)現(xiàn)一直在轉(zhuǎn)圈,或者等了好長時間才打開頁面……

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

          這一現(xiàn)象,除了網(wǎng)絡(luò)不穩(wěn)定、網(wǎng)速過慢等原因,大多數(shù)都是由于頁面設(shè)計不合理導(dǎo)致加載時間過長導(dǎo)致的。

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

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

          HTML由一系列的元素組成,通常稱為HTML元素。HTML 元素通常被用來定義一個網(wǎng)頁結(jié)構(gòu),基本上所有網(wǎng)頁都是這樣的 HTML 結(jié)構(gòu):

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

          其中:

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


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

          前面我們提到頁面 HTML 結(jié)構(gòu)不合理可能會導(dǎo)致頁面響應(yīng)慢,這個過程很多時候體現(xiàn)在<script><style>元素的設(shè)計上,它們會影響頁面加載過程中對 Javascript 和 CSS 代碼的處理。

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

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

          由于 JavaScript 在執(zhí)行過程中還可能會改動界面結(jié)構(gòu)和樣式,因此它們之間被設(shè)計為互斥的關(guān)系。也就是說,當(dāng) JavaScript 引擎執(zhí)行時,GUI 線程會被掛起。

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

          (1)當(dāng)我們打開官網(wǎng)的時候,瀏覽器會從服務(wù)器中獲取到 HTML 內(nèi)容。

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

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

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

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

          (5)當(dāng) JavaScript 腳本下載完成后,瀏覽器的控制權(quán)轉(zhuǎn)交給 JavaScript 引擎。當(dāng)腳本執(zhí)行完成后,控制權(quán)會交回給渲染引擎,渲染引擎繼續(xù)往下解析 HTML 頁面。

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

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

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

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

          百度首頁

          三、DOM 解析

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

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


          二、HTML 與 DOM 有什么不同

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

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

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

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


          三、DOM 解析

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

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

          打開控制臺 Elements 面板,可以看到這樣的 HTML 結(jié)構(gòu),如下圖所示:

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


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

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


          四、事件委托

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

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

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

          使用事件委托,可以通過將事件添加到它們的父節(jié)點,而將事件委托給父節(jié)點來觸發(fā)處理函數(shù):

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

          這樣能解決什么問題呢?

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

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

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

          注意:

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


          五、總結(jié)

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


          主站蜘蛛池模板: 久久青青草原一区二区| 日韩伦理一区二区| 国产精品丝袜一区二区三区| 久久精品视频一区| 在线视频一区二区三区四区| 一区二区三区午夜视频| 国产女人乱人伦精品一区二区| 中文字幕在线一区| 日本一区二区三区在线网| 精品女同一区二区三区免费播放 | 亚洲一区二区三区国产精品| 免费无码一区二区三区蜜桃| 国产精品第一区揄拍| 精品一区二区三区视频在线观看 | 亚洲欧洲一区二区| 国产一区二区三区手机在线观看| 99无码人妻一区二区三区免费| 一区二区手机视频| 久久精品无码一区二区三区免费| 久久久不卡国产精品一区二区| 无码人妻啪啪一区二区| 国内偷窥一区二区三区视频| 国产一区二区三区在线观看影院| 日本一区二区在线| 精品国产不卡一区二区三区 | 综合人妻久久一区二区精品| 亚洲成AV人片一区二区| 国产日韩一区二区三免费高清| 精品国产免费一区二区| 亚洲精品精华液一区二区| 激情一区二区三区| 无码视频一区二区三区在线观看| 国产在线不卡一区二区三区| 国产丝袜视频一区二区三区| 亚洲国产激情一区二区三区| 国产免费私拍一区二区三区| 成人一区二区三区视频在线观看| 亚洲综合在线一区二区三区| 亚洲AV香蕉一区区二区三区| 极品少妇一区二区三区四区| 午夜一区二区免费视频|