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)
+    }
复制代码

测试一下

好了,如今咱们完成了第二步 模板编译,下一篇咱们来完成最后一步 发布订阅模式

参考资料

相关文章
相关标签/搜索