Vue 模板解釋

Vue 模板解釋

  現在的前端三大框架都有它們獨特的模板,模板的做用就是讓開發編碼變得更加簡單,然而我以爲 Vue 在這一點上是作得近乎完美的(固然,只是我的觀點~~),Vue 模板解釋的核心不外乎就是兩個玩意兒,一個是雙大括號表示式,另外一個是模板指令,這兩東西也是咱們在 Vue 項目中都確定會用到的,下面就來詳細介紹他們是如何實現的。前端

(一)建立模板解釋對象vue

function Vue(options) {
    // 將配置對象保存在實例對象上
    this.$options = options
    // 將配置對象裏面的data屬性保存在實例對象上
    let data = this._data = options.data
    // 保存實例對象,其實也能夠用箭頭函數~~
    let me = this
    // 遍歷data中的屬性,逐一實現數據劫持
    Object.keys(data).forEach(function (key) {
        me._proxy(key)
    })
    // 模板解釋
    this.$compile = new Compile(options.el || document.body,this)
}

可見,模板解釋是在數據劫持以後實現的,在實現完數據劫持後,建立模板解釋對象,而且保存到實例對象中,這裏面有兩個參數,第一個就是配置對象中的 el ,也就是掛載的 DOM ,第二個就是 vm 。node

(二)經過 Fragment 容器實現初始化正則表達式

function Compile(el, vm) {
    // 保存vm
    this.$vm = vm
    // 保存el,判斷是不是元素節點,若是不是則嘗試經過選擇器字符來解釋
    this.$el = this.isElementNode(el) ? el : document.querySelector(el)
    // 確保$el存在
    if(this.$el){
        // 1. 取出el中全部子節點, 封裝在一個fragment對象中
        this.$fragment = this.node2Fragment(this.$el)
        // 2. 編譯fragment中全部層次子節點,這個就是模板編譯的核心方法~~~
        this.init()
        // 3. 將fragment添加到el中
        this.$el.appendChild(this.$fragment)
    }
}

初始化的過程也是很容易理解,分三步,先將全部的元素轉移到 fragment 容器中,而後在 fragment 容器中進行初始化,最後將這個 fragment 容器塞回原處。其實 fragment 容器並不進入頁面,這裏塞回去的僅僅是那些給初始化的節點而已。上面用到的三個定義在原型上的函數,isElementNode 用於判斷是不是元素節點;node2Fragment 用於將節點中的全部子節點轉移到 fragment 容器中,init 是初始化的核心函數,用於初始化模板數據:數組

Compile.prototype = {
    // 將節點中的全部子節點轉移到fragment容器中
    node2Fragment:function(node){
        // 建立一個fragment對象
        let fragment = document.createDocumentFragment()
        // 循環將元素節點中的全部子節點塞入fragment容器中,最終返回塞滿子節點的fragment對象
        let child
        while(child = node.firstChild){
            fragment.appendChild(child)
        }
        return fragment
    },
    // 判斷是不是元素節點
    isElementNode:function (node) {
        return node.nodeType === 1
    }
}

(三)初始化,詳解 init 方法app

Compile.prototype = {
    init:function(){
        // 編譯函數
        this.compileElement(this.$fragment)
    },
    compileElement:function(el){
        // 獲取全部子節點
        const childNodes = el.childNodes
        // 保存compile對象
        const me = this
        // 將類數組轉化爲真數組,遍歷全部子節點
        Array.prototype.slice.call(childNodes).forEach(function (node) {
            // 獲得節點的文本內容
            const text = node.textContent
            // 定義正則表達式,用於匹配大括號表達式
            const reg = /\{\{(.*?)\}\}/
            // 元素節點
            if(me.isElementNode(node)){
                // 編譯元素節點的指令屬性
                me.compile(node)
            }else if(me.isTextNode(node) && reg.test(text)){ // 若是是一個大括號表達式的文本節點
                me.compileText(node,RegExp.$1)
            }
            // 若是子節點還有子節點,遞歸調用
            if(node.childNodes && node.childNodes.length){
                me.compileElement(node)
            }
        })
    },
}

首先,init 方法去調用了compileElement 方法,該方法的主要做用就是處理以前準備好的 fragment 容器,將容器中全部子節點取出,而後進行分類處理,若是是一個元素節點,就去編譯元素節點中的指令,若是是一個大括號表達式的文本節點,就去編譯大括號表達式;若是節點裏面還有子節點,則遞歸調用。順着這個思路,先來研究比較簡單的大括號表達式的狀況(就是compileText這個方法):框架

Compile.prototype = {
    // 編譯大括號表達式,參數node表明節點,exp表明表達式(就是正則匹配到的那個東西)
    compileText:function(node,exp){
        compileUtil.text(node, this.$vm, exp)
    }
}

const compileUtil = {
    // 解釋 v-text 和 雙大括號表達式,由此也能夠看出其實雙大括號表達式跟v-text指令的實現原理是一致的!
    text:function (node, vm, exp) {
        this.bind(node,vm,exp,'text')
    },
    // 真正用於解釋指令的函數
    bind:function (node, vm, exp, dir) {
        // 獲取更新函數
        const updaterFn = updater[dir + 'Updater']
        updaterFn && updaterFn(node,this._getVMVal(vm,exp))
    },
    // 獲得表達式對應的value
    _getVMVal:function (vm, exp) {
        let val = vm._data
        exp = exp.split('.')
        exp.forEach(function (key) {
            val = val[key]
        })
        return val
    }
}
// 更新器
const updater = {
    // 更新節點的textContent
    textUpdater:function (node, value) {
        node.textContent = typeof value === 'undefined' ? '' : value
    }
}

從代碼和註釋上已經很好的說明了整個流程了,這裏再簡單的囉嗦一下吧,其實咱們用的雙大括號表達式也是一種指令,由於它跟v-text的處理是徹底一致的,都是在操做節點的textContent屬性。可能會讓人迷糊的是 _getVMVal函數吧,這個函數的做用就是處理多層次對象的,由於表達式不會僅僅是一層的,也多是兩層或者多層次的,好比,data裏面保存了一個person對象,裏面還有name等其餘屬性,然而咱們極可能會在表達式裏面寫person.name這樣相似的多層次的屬性(說句題外話,vue 不會監聽到對象內部屬性的變化,若是是簡單的經過對象.屬性名的方式去改變對象,那麼vue是不知道的~~),這個函數也正是用於處理這種結構的。由於雙大括號跟其餘指令都非常相似的思想,都是在操做 DOM 的某個屬性,具體的過程就再也不細說了。函數

相關文章
相關標籤/搜索