缓存抽象语法树

331 阅读2分钟

所谓的抽象语法树,就是由html与 {{xxx}} 拼接上的模板字符串

缓存抽象语法树的原因?

​ 大家都知道vue中的数据是双向绑定的,也就是说,数据是能够被改变的,每当数据改变,重新生成虚拟dom,将数据加入到虚拟dom,重新渲染页面,这个过程是非常消耗性能的

  1. 定义一个Vue的构造函数
    function JGVue(options) {
        this._template = document.querySelector(options.el);
        this._data = options.data;
        this.mount(); // 将html模板与数据都挂载到页面中
    } 
    
  2. 将html模板与数据都挂载到页面中的mount函数
    JGVue.prototype.mount = function() { // 将html模板与数据都挂载到页面中
        let data_vnode = this.creteRenderFn(); // 缓存抽象语法树
        data_vnode();
    }
    
  3. 缓存抽象语法树的函数
    JGVue.prototype.creteRenderFn = function() {
        let vnode = getVNode(this._template); // 获取到虚拟DOM
        return () => {
            let tmp = combine(vnode,this._data); // 将带 {{xxx}} 的虚拟dom转换为 带真实数据的虚拟dom 
            console.log('tmp',tmp);
        }
    }
    
  4. 构造虚拟dom
    class VNode { // 构造虚拟dom
        constructor(tag,data,value,type) {
            this._tag = tag && tag.toLowerCase();
            this._data = data;
            this._value = value;
            this._type = type;
            this._children = [];
        }
        appendChild(vnode) {
            this._children.push(vnode);
        }
    }
    
  5. 将真实的dom转换为虚拟dom
     function getVNode(node) { // 将真实的dom转换为虚拟dom
                let nodeType = node.nodeType;
                let _vnode = null;
                if(nodeType === 1) { // 元素节点
                    let _tag = node.nodeName;
                    let attrs = node.attributes;
                    let attrsObj = {};
                    for (let i = 0; i < attrs.length; i++) {
                        attrsObj[attrs[i].nodeName] = attrs[i].nodeValue;
                    }
                    _vnode = new VNode(_tag,attrsObj,undefined,nodeType);
    
                    // 含有子节点
                    let _childres = node.childNodes;
                    for (let j = 0; j < _childres.length; j++) {
                        let childNode = getVNode(_childres[j]);
                        _vnode.appendChild(childNode);
                    }
                } else if(nodeType === 3) { // 文本节点
                    _vnode = new VNode(undefined,undefined,node.nodeValue,nodeType);
                }
                return _vnode;
            }
    
  6. 将带 {{}} 的虚拟dom转换为 带数据的虚拟dom
     function combine(vnode,data) { // vnode 指虚拟dom模板  data指data:{name:'zs',age:30,gender:'女'} 将带 {{}} 的虚拟dom转换为 带数据的虚拟dom 
                let _tag = vnode._tag;
                let _data = vnode._data;
                let _children = vnode._children;
                let _type = vnode._type;
                let _value = vnode._value;
                let _vnode = null;
                if(_type === 1) { // 元素节点
                    _vnode = new VNode(_tag,_data,_value,_type);
                    // 子节点
                    for (let i = 0; i < _children.length; i++) {
                        let childNode = combine(_children[i],data);                    
                        _vnode.appendChild(childNode);
                    } 
                } else if(_type === 3) { // 文本节点
                    /**
                        需要匹配到 {{xxxx}} 再次匹配到 xxx,
                        然后根据 xxx 从data中知道对应的值,并将其渲染到页面中
                    */
                    let reg = /\{\{(.+?)\}\}/g;
                    _value = _value.replace(reg,($1,$2) => {
                        return getValueByPath($2.trim(),data);
                    })
                    _vnode = new VNode(_tag,_data,_value,_type);
                }
                return _vnode;
            }
    
  7. 处理多级路径的问题 {{ name.first.second }}
    function getValueByPath(path,data) { // path 对应的是多级路径  data对应的是 Vue中的data
        let res = data;
        let arr_path = path.split('.');
        let props;
        while(props = arr_path.shift()) {
            res = data[props];
        }
        return res;
    }
    
  8. 将VUE实例化
    let app = new JGVue({
        el:'#root',
        data:{
            name: 'zs',
            age: 30,
            gender:'女',
            hobby:{
                eat:{
                    hamburg:'汉堡包'
                }
            }
        }
    })