vue解析指令compile源碼層面使用解析

概述

上篇文章我們已經介紹瞭Vue的響應式原理,並實現瞭對數據的監聽,監聽的目的是為瞭及時更新視圖,所以這篇文章就來介紹下vue解析指令並初始化視圖部分。

compile

在Vue的構造函數中對根元素進行編譯

class MVue {
  constructor (options) {
    // 保存options
    this.$options = options
    this.$data = options.data
    // 將data進行響應式處理
    observe(this.$data)
    // 代理
    proxy(this)
    // 編譯 
    /**核心部分**/
    new Compile(options.el, this)
  }
}
class Compile {
  constructor (el, vm) {
    this.$vm = vm
    this.$el = document.querySelector(el)

    if (this.$el) {
      this.compile(this.$el)
    }
  }
  compile (node) {
    // 找到該元素的子節點
    const childNodes = node.childNodes
    // childNodes是類數組對象
    Array.from(childNodes).forEach(n => {
      // 判斷類型
      if (this.isElment(n)) {
        this.compileElement(n)
        // 遞歸
        if (n.childNodes.length > 0) {
          this.compile(n)
        }
      } else if (this.isInter(n)) {
        // 動態插值表達式  編譯文本
        this.compileText(n)
      }
    })
  }
  isElment (node) {
    return node.nodeType === 1
  }
  isInter (n) {
    return n.nodeType === 3 && /\{\{(.*)\}\}/.test(n.textContent)
  }
  isDir (attrName) {
    return attrName.startsWith('m-')
  }
  isEvent (attrName) {
    return attrName.startsWith('@')
  }
  // 編譯元素:遍歷它的所有屬性,看是否m-開頭指令,或者@事件
  compileElement (node) {
    const attrs = node.attributes
    Array.from(attrs).forEach(attr => {
      // m-text="XXX"
      // name = m-text, value=xxx
      const attrName = attr.name
      const exp = attr.value
      // 是指令
      if (this.isDir(attrName)) {
        // 執行特定指令處理函數
        const dir = attrName.substring(2)
        this[dir] && this[dir](node, exp)
      } else if (this.isEvent(attrName)) {
      	//是事件
      	const event = attrName.substring(1)
        // 通過bind改變this指向為vm實例,以便方法內部訪問vm.data中的數據
        node.addEventListener(event, this.$vm.$methods[exp].bind(this.$vm))
      }
    })
  }
  compileText (n) {
    // 獲取表達式
    // n.textContent = this.$vm[RegExp.$1]
    n.textContent = this.$vm[RegExp.$1.trim()]
  }
  text (node, exp) {
    node.textContent = this.$vm[exp] || exp
  }
  html (node, exp) {
    node.innerHTML = this.$vm[exp]
  }
  model (node, exp) {
    node.value = this.$vm[exp]
  }
}

測試代碼

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
    <div id="app">
        <p>{{ counter }}</p>
        <p m-text="測試"></p>
        <p m-text="counter"></p>
        <p m-html="desc"></p>
        <input m-model="name" />
    </div>
</body>
<script src="./index.js"></script>
<script>
    const app = new MVue({
            el: '#app',
            data: {
                counter: 1,
                desc: '<span style = "color: red">super wanan</span>',
                name: 'wanan'
            }
        })
        // setInterval(() => {
        //     app.counter++
        // }, 1000)
</script>
</html>

結果

延伸及重點講解

1. 類數組對象

node.childNodes和node.attributes都是一種類數組對象,NodeList用於保存一組有序的節點。可以通過方括號語法來訪問NodeList的值,有item方法與length屬性。它並不是Array的實例,沒有數組對象的方法。

比如我們再compile方法中打印childNodes,會得到這樣的結果:

因此需要使用Array.from()方法將類數組對象轉換為真正數組。

2. RegExp.$1

RegExp 是javascript中的一個內置對象。為正則表達式。

RegExp.$1是RegExp的一個屬性,指的是與正則表達式匹配的第一個 子匹配(以括號為標志)字符串,以此類推,RegExp.$2,RegExp.$3,…RegExp.$99總共可以有99個匹配

其實RegExp這個對象會在我們調用瞭正則表達式的方法後, 自動將最近一次的結果保存在裡面, 所以如果我們在使用正則表達式時, 有用到分組, 那麼就可以直接在調用完以後直接使用RegExp.$xx來使用捕獲到的分組內容。

所以我們在compileText時可以這樣使用:

  compileText (n) {
    // 獲取表達式
    // n.textContent = this.$vm[RegExp.$1]
    n.textContent = this.$vm[RegExp.$1.trim()]
  }

需要註意的是,上述代碼中還使用瞭trim方法去除前後空格,這是為瞭將{{ counter }}前後的空格去掉,正確匹配到this.vm[‘counter’],否則會讀取this.vm[’ counter ']導致讀取不到。

3. nodeType

DOM(文檔對象模型)可以將任何HTML和XML文檔描繪成一個由多層node(節點)構成的結構。

DOM1級定義瞭一個Node接口,該接口將由DOM中的所有節點類型實現。這個Node接口在JavaScript中作為Node類型實現的。除瞭IE之外,在其他所有瀏覽器中都可以訪問到這個類型。JavaScript中的所有節點類型都繼承自Node類型,因此所有節點類型都共享相同的基本屬性和方法。每個節點都有一個nodeType屬性,用於表明節點的類型。

nodeType的取值如下:

類型 nodeType常數值 描述
Node.ELEMENT_NODE 1 元素節點
Node.ATTRIBUTE_NODE 2 屬性節點
Node.TEXT_NODE 3 文本節點
Node.CDATA_SECTION_NODE 4 字符數據節點(文本不會被解析器解析)
Node.ENTITY_REFERENCE_NODE 5 實體引用節點
Node.ENTITY_NODE 6 實體節點
Node.PROCESSING_INSTRUCTION_NODE 7 處理指令節點
Node.COMMENT_NODE 8 註釋節點
Node.DOCUMENT_NODE 9 文檔節點(DOM樹的根節點)
Node.DOCUMENT_TYPE_NODE 10 向為文檔定義的實體提供接口
Node.DOCUMENT_FRAGMENT_NODE 11 表示鄰接節點和它們的子樹
Node.NOTATION_NODE 12 代表一個符號在DTD中的聲明

到此這篇關於vue解析指令compile源碼層面使用解析的文章就介紹到這瞭,更多相關vue compile內容請搜索WalkonNet以前的文章或繼續瀏覽下面的相關文章希望大傢以後多多支持WalkonNet!

推薦閱讀: