Vue響應式原理簡易Mvvm三步走第二步 (模板解析)

前言

上一篇咱們完成了第一步 (數據劫持),從而完成了對屬性的監聽,這一篇咱們來完成第二步(模板解析)vue

在開始以前,咱們須要對數據進行一層代理,這樣咱們能夠更簡潔的來調用屬性瀏覽器

// 在 vue 中咱們使用屬性是這樣的
    vm.name
    
    // 而不是這樣的
    vm._data.name
複製代碼

數據代理

數據代理其實就至關於將 vm._data 中的屬性作了一層映射, 代理到 vm 上。bash

// mvvm.js
    class Mvvm {
        constructor(options = {}) {
            this.$options = options;
            this._data = this.$options.data;
            observe(this._data)
            
            // + 號表明新增代碼
        +   dataProxy(this, this._data)
        }
    }    
    
    // 代理函數
+    function dataProxy(vm, data) {
+        for(let key in data) {
             // vm 代理 vm._data上的屬性
+            Object.defineProperty(vm, key, {
+                configurable: true,
+                get() {
+                    return vm._data[key] // 實際返回的仍然是 vm._data的屬性值
+                },
+                set(newVal) {
+                    vm._data[key] = newVal // 修改vm._data的屬性值
+                }
+            })
+        }
+    }
複製代碼

測試一下

打開瀏覽器控制檯app

完成了數據代理,下面開始模板解析mvvm

模板解析

// mvvm.js
    // mvvm構造函數
    class Mvvm {
        constructor(options = {}) {
            ...
            dataProxy(this, this._data)
            
            // 調用函數
        +   new Compile(this.$options.el, this)
        }
    }
    
    // 模板解析函數
+    function Compile(el, vm) {
        // 獲取根節點
        vm.$el = document.querySelector(el)
        // 建立一個空文檔片斷
        let fragment = document.createDocumentFragment();
        // 正則 用來匹配插值,即 {{ }} 中的內容
        let reg = /\{\{(.*?)\}\}/g;
        
        // 將根節點中的子幾點依次添加到 文檔片斷中
        while(child = vm.$el.firstChild) {
            // 小知識點:在使用appendChild時
            // 若是文檔樹中已經存在了 child,child將從文檔樹中刪除,而後從新插入它的新位置。
            // 因此當vm.$el中的節點所有插入到fragment中時,child = null,循環終止
            fragment.appendChild(child)
        }
        
        // 替換 {{}} 種的內容
        function replace(frg) {
            frg.childNodes.forEach(node => {
                let txt = node.textContent
                
                // 文本節點 而且有{{}}
                if(node.nodeType === 3 && reg.test(txt)) {
                    function replaceTxt() {
                        node.textContent = txt.replace(reg, (matched, placeholder) => {   
                            console.log(placeholder)       // 匹配到的內容 a.b.c
                            
                            return placeholder.split('.').reduce((val, key) => {
                                return val[key];           // val = vm.a.b.c
                            }, vm);
                        });
                    };
                    // 替換
                    replaceTxt();
                }
                
                // 遞歸 替換更層次節點
                if(node.childNodes && node.childNodes.length) {
                    replace(node)
                }
            })
        }
        replace(fragment)
        // 將文檔片斷插入根節點
        vm.$el.appendChild(fragment)
+    }
複製代碼

測試一下

好了,如今咱們完成了第二步 模板編譯,下一篇咱們來完成最後一步 發佈訂閱模式

參考資料

相關文章
相關標籤/搜索