首頁 > 軟體

js的一些潛在規則範例分析

2023-02-24 06:01:52

宏任務和微任務

採納 JSC 引擎的術語,我們把宿主發起的任務稱為宏觀任務,把 JavaScript 引擎發起的任務稱為微觀任務。

JavaScript 引擎等待宿主環境分配宏觀任務,在作業系統中,通常等待的行為都是一個事件迴圈,所以在 Node 術語中,也會把這個部分稱為事件迴圈。在底層的 C/C++ 程式碼中,這個事件迴圈是一個跑在獨立執行緒中的迴圈。

宏觀任務的佇列就相當於事件迴圈。

在宏觀任務中,JavaScript 的 Promise 還會產生非同步程式碼,JavaScript 必須保證這些非同步程式碼在一個宏觀任務中完成,因此,每個宏觀任務中又包含了一個微觀任務佇列。

語句的執行過程 (Completion Record )

我們知道有的語句按順序執行,有的語句會阻斷執行。那麼這是何種原因導致的呢?

我們來看一下js語句執行的完成狀態。JavaScript 語句執行的完成狀態,我們用一個標準型別來表示:Completion Record。

Completion Record 表示一個語句執行完之後的結果,它有三個欄位:

  • [[type]] 表示完成的型別,有 break continue return throw 和 normal 幾種型別;如果返回的type是normal,那麼語句將會順序執行。
  • [[value]] 表示語句的返回值,如果語句沒有,則是 empty;只有表示式語句會產生 [[value]]。
  • [[target]] 表示語句的目標,通常是一個 JavaScript 標籤。當在迴圈語句中,結合break/continue可以跳出多層迴圈。
outer: while(true) {
    inner: while(true) {
        break outer;
    }
}
console.log("finished")

在任何一個js語句之前都可以加一個標籤。

    firstStatement: var i = 1;

控制語句跟 break 、continue 、return 、throw四種型別與控制語句兩兩組合產生的效果。

  • 消費就是在當前語句中結束了。

穿透就是繼續執行下一條語句。

文法

文法 = 詞法 + 語法。

詞法

JavaScript 原始碼中的輸入可以這樣分類:

WhiteSpace 空白字元

LineTerminator 換行符

Comment 註釋

Token 詞

  • IdentifierName 識別符號名稱,典型案例是我們使用的變數名,注意這裡關鍵字也包含在內了。
  • Punctuator 符號,我們使用的運運算元和大括號等符號。
  • NumericLiteral 數位直接量,就是我們寫的數位。 為什麼12.toString會報錯?

十進位制的 Number 可以帶小數,小數點前後部分都可以省略,但是不能同時省略。12.被當成一個詞。如果想讓表示式正常執行,我們可以讓.成為一個詞。

    12. toString()
  • StringLiteral 字串直接量,就是我們用單引號或者雙引號引起來的直接量。

字串中其他必須跳脫的字元是和所有換行符。

  • Template 字串模板,用反引號`括起來的直接量。
  • RegularExpressionLiteral

正規表示式有自己的語法規則,在詞法階段,僅會對它做簡單解析。

語句是否需要加分號

自動插入分號規則其實獨立於所有的語法產生式定義,它的規則說起來非常簡單,只有三條。

  • 要有換行符,且下一個符號是不符合語法的,那麼就嘗試插入分號。
  • 有換行符,且語法中規定此處不能有換行符,那麼就自動插入分號。
  • 原始碼結束處,不能形成完整的指令碼或者模組結構,那麼就自動插入分號。

no LineTerminator here規則

這個規則與自動插入分號的第二條規則緊密相關。

指令碼和模組

指令碼是可以由瀏覽器或者 node 環境引入執行的,而模組只能由 JavaScript 程式碼用 import引入執行。

從概念上,我們可以認為指令碼具有主動性的 JavaScript 程式碼段,是控制宿主完成一定任務的程式碼;而模組是被動性的 JavaScript 程式碼段,是等待被呼叫的庫。

直接 import 一個模組,只是保證了這個模組程式碼被執行,參照它的模組是無法獲得它的任何資訊的。帶 from 的 import 意思是引入模組中的一部分資訊,可以把它們變成原生的變數。

通過export default匯出的值,和匯入檔案的變數不是實時繫結的。匯出檔案的變數改變不會影響匯入變數的變化。

宣告提升

預處理階段,var 和函數宣告的作用能夠穿透一切語句結構,它只認指令碼、模組和函數體三種語法結構。

函數宣告提升和var變數宣告提升的區別

函數宣告能穿過if等語句,但是隻是在全域性建立一個同名的變數賦值為undefined,並沒有把函數體提升。

    console.log(foo); // undefined
    if(true) {
        function foo(){}
    }
    // 因為一般函數都是整體提升的。
    var a = 1;
    function foo() {
        console.log(a); // undefined
        if(false) {
            var a = 2;
        }
    }
    foo();

解析HTML

編譯階段。會將html標籤拆分成一個個token(表示最小的有意義的單元), 種類大約只有標籤開始、屬性、標籤結束、註釋、CDATA 節點幾種。

實現分詞,又用到了狀態機。用狀態機做詞法分析,其實正是把每個詞的“特徵字元”逐個拆開成獨立狀態,然後再把所有詞的特徵字元鏈合起來,形成一個聯通圖結構。 其中每一個狀態函數都返回一個狀態函數,做狀態遷移。

把html元素分成若干詞後,我們就可以構建dom樹了。這個過程是使用棧來實現的。我們把每個解析的詞加入到棧中,當接收完所有輸入,棧頂就是最後的根節點。

對於 Text 節點,我們則需要把相鄰的 Text 節點合併起來,我們的做法是當詞(token)入棧時,檢查棧頂是否是 Text 節點,如果是的話就合併 Text 節點。

可以點選這裡檢視解析規則

github上別人寫了一個簡易的解析器

排版。

  • 瀏覽器對行的排版,一般是先行內佈局,再確定行的位置,根據行的位置計算出行內盒和文字的排版位置。
  • 塊級盒比較簡單,它總是單獨佔據一整行,計算出交叉軸方向的高度即可。
  • 浮動元素排版,float 元素非常特別,瀏覽器對 float 的處理是先排入正常流,再移動到排版寬度的最左 /最右(這裡實際上是主軸的最前和最後)。
  • 絕對定位元素。完全跟正常流無關的一種獨立排版模式,逐層找到其父級的 position 非 static 元素即可。

渲染。

瀏覽器中渲染這個過程,就是把每一個元素對應的盒變成點陣圖。 這裡的元素包括 HTML 元素和偽元素,一個元素可能對應多個盒(比如 inline 元素,可能會分成多行)。每一個盒對應著一張點陣圖。

渲染過程,是不會把子元素繪製到渲染的點陣圖上的,這樣,當父子元素的相對位置發生變化時,可以保證渲染的結果能夠最大程度被快取,減少重新渲染。

合成。

合成的過程,就是為一些元素建立一個“合成後的點陣圖”(我們把它稱為合成層),把一部分子元素渲染到合成的點陣圖上面。

繪製。

繪製過程,實際上就是按照 z-index 把合成點陣圖依次繪製到螢幕上。

DOM API

DOM API 大致會包含 4 個部分。

  • 節點:DOM 樹形結構中的節點相關 API。
  • 事件:觸發和監聽事件相關 API。
  • Range:操作文字範圍相關 API。
  • 遍歷:遍歷 DOM 需要的 API。

節點

元素在DOM樹中關係api

  • parentNode
  • childNodes
  • firstChild
  • lastChild
  • nextSibling
  • previousSibling

操作 DOM 樹的API

  • appendChild
  • insertBefore
  • removeChild
  • replaceChild

一些高階 API

  • compareDocumentPosition 是一個用於比較兩個節點中關係的函數。
  • contains 檢查一個節點是否包含另一個節點的函數。這個方法一般用於做一些點選判斷,然後關閉一些dom的功能。
  • isEqualNode 檢查兩個節點是否完全相同。
  • isSameNode 檢查兩個節點是否是同一個節點,實際上在 JavaScript 中可以用“===”。
  • cloneNode 複製一個節點,如果傳入引數 true,則會連同子元素做深拷貝。

建立DOM的api

  • createElement
  • createTextNode
  • createCDATASection
  • createComment
  • createProcessingInstruction
  • createDocumentFragment
  • createDocumentType

操作屬性的api

  • getAttribute
  • setAttribute
  • removeAttribute
  • hasAttribute 如果你喜歡 property 一樣的存取 attribute,還可以使用 attributes 物件,比如document.body.attributes.class =“a”等效於document.body.setAttribute(“class”,“a”)。

查詢元素api

  • querySelector
  • querySelectorAll
  • getElementById
  • getElementsByName
  • getElementsByTagName
  • getElementsByClassName

我們需要注意,getElementById、getElementsByName、getElementsByTagName、getElementsByClassName,這幾個 API 的效能高於 querySelector。

新增加的節點會被新增到非querySelector, querySelectorAll查詢出來的物件上的。

遍歷

  • createNodeIterator
  • createTreeWalker

Range

Range API 表示一個 HTML 上的範圍,這個範圍是以文字為最小單位的,所以 Range 不一定包含完整的節點。

具體請看這裡

DOM中的位置

全域性尺寸資訊

我們獲取寬高的物件應該是“盒”,於是 CSSOM View 為 Element 類新增了兩個方法:

  • getClientRects()。返回一個列表,裡面包含元素對應的每一個盒所佔據的使用者端矩形區域,這裡每一個矩形區域可以用 x, y, width, height 來獲取它的位置和尺寸。
  • getBoundingClientRect()。它返回元素對應的所有盒的包裹的矩形區域,需要注意,這個 API 獲取的區域會包括當 overflow 為visible 時的子元素區域。

這兩個 API 獲取的矩形區域都是相對於視口的座標,這意味著,這些區域都是受捲動影響的。

事件

事件捕獲的由來?

我們操作元素時,都是通過輸入裝置來做到的,點選事件來自觸控式螢幕或者滑鼠,滑鼠點選並沒有位置資訊,但是一般作業系統會根據位移的累積計算出來,跟觸控式螢幕一樣,提供一個座標給瀏覽器。把這個座標轉換為具體的元素上事件的過程,就是捕獲過程了。

建議這樣使用冒泡和捕獲機制:預設使用冒泡模式,當開發元件時,遇到需要父元素控制子元素的行為,可以使用捕獲機制。

事件處理常式不一定是函數,也可以是個 JavaScript 具有 handleEvent 方法的物件。

var o = {
    handleEvent: event => console.log(event)
}
document.body.addEventListener("keydown", o, false);

自定義事件。DOM API 中的事件並不能用於普通物件,所以很遺憾,我們只能在 DOM 元素上使用自定義事件。

    var evt = new Event("look", {
        "bubbles":true, 
        "cancelable":false
    });
    document.dispatchEvent(evt); // 呼叫自定義事件

效能優化

以上就是js的一些潛在規則範例分析的詳細內容,更多關於js潛在規則的資料請關注it145.com其它相關文章!


IT145.com E-mail:sddin#qq.com