2020年11月18日

数据驱动模型(4)

作者 rourou

生成带缓存的AST(createRenderFn),每次数据改变时就会生成一个新的虚拟dom,然后通过diff算法与原虚拟dom进行对比修改(update),然后更新html,渲染(render)

根据前3篇文章,可初步实现vue简化版本的数据渲染。(数据未响应化,后续会更新)

<script>
    function JGVue(option){
      this._data = option.data;
      this._el = document.querySelector(option.el);
      this._parent = this._el.parentNode;
      // 挂载
      this.mount();
    }

    JGVue.prototype.mount = function(){
      this.render = this.createRenderFn();
      this.mountComponent();
    }
    JGVue.prototype.mountComponent = function () {
      // 执行 mountComponent() 函数 
      // 挂载组件(需使用发布订阅模式,渲染和计算的行为交给watcher完成)
      let mount = () => {
        this.update( this.render() )
      }
      mount.call( this ); // 本质应该交给 watcher 来调用, 但是还没有讲到这里
    }
    JGVue.prototype.createRenderFn = function(){
      
      let ast = getVnode(this._el);
      return function render(){
        // 带数据的 ast
        let _tmp = combine( ast, this._data );
        return _tmp;
      }
    }

    JGVue.prototype.update = function (vnode) {
      // 简化, 直接生成 HTML DOM replaceChild 到页面中
      // 父元素.replaceChild(新,旧)
      let newDom = parseDom (vnode);

      this._parent.replaceChild(newDom,document.querySelector('#root'));//会将页面的DOM全部替换,vue是使用diff算法进行判断增删改。因为每次全部替换即每次需重新去获取跟元素

    }
    class VNode{
      constructor(tag, attr, value, type){
        this.tag = tag && tag.toLowerCase();
        this.attr = attr;
        this.value = value;
        this.type = type;
        this.children = [];
      }
      getData(data){
        this.children.push(data);
      }
    }
//虚拟dom
    function getVnode(el){
      var type = el.nodeType;
      var tag,value;
      var attr = {};
      var _vNode = null;
      if(type == 1){
        // 元素节点
        tag = el.nodeName;
        var attrs = el.attributes;
        for(var i = 0;i<attrs.length;i++){
          attr[ attrs[i].nodeName ] = attrs[i].nodeValue;
        }
        _vNode = new VNode(tag, attr, value, type);

        var childNode = el.childNodes;
        for(var i=0;i<childNode.length;i++){
          _vNode.getData( getVnode(childNode[i]) );
        }
        
      }else if(type == 3){
        // 文本节点
        value = el.nodeValue;
        _vNode = new VNode(tag, attr, value, type);
      }
      return _vNode;
    }
    //带数据的虚拟dom
    var rz = /\{\{(.+?)\}\}/g;
    function combine(vnode, data){
      var tag = vnode.tag;
      var attr = vnode.attr;
      var value = vnode.value;
      var type = vnode.type;
      var children = vnode.children;
      var _vNode = null;
      if(type == 1){
        // 元素节点
        _vNode =new VNode(tag, attr, value, type);

        for(var i=0;i<children.length;i++){
          _vNode.getData( combine(children[i], data) )
        }

      }else if(type == 3){
        // 文本节点
        var res = value.trim().replace(rz, function(_,g){
          return getValueByPath(data, g);
        })
        _vNode =new VNode(tag, attr, res, type);

      }
      return _vNode;
    }
    function getValueByPath(obj, path){
      var arr = path.split('.');
      let res = obj;
      let prop;
      // arr.shift()取出数组的第一个值
      while( prop = arr.shift() ){
        res = res[ prop ];
      }
      return res;
    }
//通过虚拟dom创建真正的dom
    function parseDom (res){
      let tag = res.tag;
      let type = res.type;
      let value = res.value;
      let attr = res.attr;
      let children = res.children;
      if(type == 1){
        if(tag != undefined){
          var ele = document.createElement(tag);
        }
        if(attr != undefined){
          for(var i in attr){
            ele.setAttribute(i,attr[i]);
          }
        }
        if(children.length != 0){
          for(var i=0;i<children.length;i++){
            val = ele.appendChild(parseDom(children[i]));
          }
        }
      }else if(type == 3){
        var ele = document.createTextNode(value);
      }
      return ele;
    }

    let app = new JGVue({
      el: '#root',
      data: {
        name: '肉肉',
        message: {
          info: '还是嘎嘎'
        }
      }
    })
  </script>