響應(yīng)式框架原理4(模板編譯原理&雙向綁定實(shí)現(xiàn))

響應(yīng)式框架原理3(Object.defineProperty VS Proxy)
接著之前的繼續(xù)
模板編譯原理
了解了如何監(jiān)聽數(shù)據(jù)的變化,接下來以類 Vue 框架為例,看一個(gè)典型的用法:

<body>
    <div id="app">
        <h1>{{stage}}標(biāo)題:{{course.title}}</h1>
        <p>作者:{{course.author}}</p>
        <p>時(shí)間:{{course.publishTime}} </p>
    </div>

  <script>
    let vue = new Vue({ 
      ele: '#app', 
      data: {
        stage: '簡書',
        course: {
            title: '響應(yīng)式框架原理記錄',
            author: '看到請叫我不要熬夜',
            publishTime: '2020年3月3日'
        },
    }
    })
  </script>
</body>

模版變量使用了 {{ }} 的表達(dá)方式輸出模版變量。最終輸出的 HTML 內(nèi)容應(yīng)該被合適的數(shù)據(jù)進(jìn)行填充替換,因此還需要一步編譯過程,該過程任何框架或類庫中都是相通的,比如 React 中的 JSX,也是編譯為 React.createElement,并在生成虛擬 DOM 時(shí)進(jìn)行數(shù)據(jù)填充。

簡化過程,將模版內(nèi)容輸出為真實(shí)的HTML過程:

        <div id="app">
            <h1>{{stage}}標(biāo)題:{{course.title}}</h1>
            <p>作者:{{course.author}}</p>
            <p>時(shí)間:{{course.publishTime}} </p>
        </div>

模版編譯實(shí)現(xiàn)
使用正則 + 遍歷,有時(shí)也需要一些算法知識,現(xiàn)在的場景只需要對 #app 節(jié)點(diǎn)下內(nèi)容進(jìn)行替換,通過正則識別出模版變量,獲取對應(yīng)的數(shù)據(jù)即可:

        const data = {
            stage: '簡書',
            course: {
                title: '響應(yīng)式框架原理記錄',
                author: '看到請叫我不要熬夜',
                publishTime: '2020年3月3日'
            },
        }
        compile(document.querySelector('#app'), data)

        function compile(el, data) {
            let fragment = document.createDocumentFragment()

            while (child = el.firstChild) {
                fragment.appendChild(child)
            }
            // 對el里面的內(nèi)容進(jìn)行替換
            function replace(fragment) {
                Array.from(fragment.childNodes).forEach(node => {
                    let textContent = node.textContent
                    let reg = /\{\{(.*?)\}\}/g
                    if (node.nodeType === 3 && reg.test(textContent)) {
                        const nodeTextContent = node.textContent
                        const replaceText = () => {
                            node.textContent = nodeTextContent.replace(reg, (matched, placeholder) => {
                                return placeholder.split('.').reduce((prev, Key) => {
                                    return prev[Key]
                                }, data)
                            })
                        }
                        replaceText()
                    }
                    // 如果還有子節(jié)點(diǎn),繼續(xù)遞歸 replace
                    if (node.childNodes && node.childNodes.length) {
                        replace(node)
                    }
                })
            }
            replace(fragment)
            el.appendChild(fragment)
            return el
        }

使用 fragment 變量儲(chǔ)存生成的真實(shí) HTML 節(jié)點(diǎn)內(nèi)容。通過 replace 方法對 {{變量}} 進(jìn)行數(shù)據(jù)替換,同時(shí) {{變量}} 的表達(dá)只會(huì)出現(xiàn)在 nodeType === 3 的文本類型節(jié)點(diǎn)中,因此對于符合 node.nodeType === 3 && reg.test(textContent) 條件的情況,進(jìn)行數(shù)據(jù)獲取和填充。我們借助字符串 replace 方法第二個(gè)參數(shù)進(jìn)行一次性替換,此時(shí)對于形如 {{data.course.title}} 的深層數(shù)據(jù),通過 reduce 方法,獲得正確的值。

因?yàn)?DOM 結(jié)構(gòu)可能是多層的,所以對存在子節(jié)點(diǎn)的節(jié)點(diǎn),依然使用遞歸進(jìn)行 replace 替換。

編譯過程比較簡單,沒有考慮到邊界情況,只是單純完成模版變量到真實(shí) DOM 的轉(zhuǎn)換,你品,細(xì)品


雙向綁定實(shí)現(xiàn)

上面例子實(shí)現(xiàn)是單向的,數(shù)據(jù)變化引發(fā)視圖變化,接下來在頁面中存一個(gè)輸入框,觸發(fā)數(shù)據(jù)變化:

        測試:<input type="text" v-model="inputValue"/>

思路:在模板編譯過程中,對存在v-model屬性的node進(jìn)行事件監(jiān)聽,在輸入框輸入時(shí)候,改變v-model對應(yīng)的數(shù)據(jù)(inputValue),增加compile中replace方法邏輯,對于 node.nodeType === 1 的DOM類型。

        const data = {
            stage: '簡書',
            inputValue: '',
            course: {
                title: '響應(yīng)式框架原理記錄',
                author: '看到請叫我不要熬夜',
                publishTime: '2020年3月3日'
            },
        }
        compile(document.querySelector('#app'), data)

        function compile(el, data) {
            let fragment = document.createDocumentFragment()

            while (child = el.firstChild) {
                fragment.appendChild(child)
            }
            // 對el里面的內(nèi)容進(jìn)行替換
            function replace(fragment) {
                Array.from(fragment.childNodes).forEach(node => {
                    let textContent = node.textContent
                    let reg = /\{\{(.*?)\}\}/g
                    if (node.nodeType === 3 && reg.test(textContent)) {
                        const nodeTextContent = node.textContent
                        const replaceText = () => {
                            node.textContent = nodeTextContent.replace(reg, (matched, placeholder) => {
                                return placeholder.split('.').reduce((prev, Key) => {
                                    return prev[Key]
                                }, data)
                            })
                        }
                        replaceText()
                    }
                    
                    if(node.nodeType === 1) {
                        let attributesArray = node.attributes
                        Array.from(attributesArray).forEach(attr => {
                            let attributeName = attr.name
                            let attributeValue = attr.value
                            if(name.includes('v-')) {
                                node.value = data[attributeValue]
                            }
                            node.addEventListener('input', e => {
                                let newVal = e.target.value
                                data[attributeValue] = newVal
                                console.log(data)
                            })
                        })
                        
                    }
                    // 如果還有子節(jié)點(diǎn),繼續(xù)遞歸 replace
                    if (node.childNodes && node.childNodes.length) {
                        replace(node)
                    }
                })
            }
            replace(fragment)
            el.appendChild(fragment)
            return el
        }
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容