2020年11月17日

数据驱动模型(2)

作者 rourou

数据驱动模型(1)文档中初步仿写了vue的数据驱动(简化版)实现了数据填充到“坑”当中,但是在demo中还有以下几方面优化:

  • 1、只考虑了单属性 ({{name}}),未考虑多层级关系({{child.name}})
  • 2、代码未整合
  • 3、虚拟DOM

多层级关系解析(如:{{child.name}})

obj为数据对象,path为所取的索引。首先将对象索引通过’.’分割符将其转化为数组形式,在通过判断数组中值向下取值直到判断为false时。

function getLog(obj , path){
      let arr = path.split('.');
      let prop;
      // arr.shift()取出数组的第一个值
      while( prop = arr.shift() ){
        obj = obj[prop];
      }
      return obj;
    }
var data = {child:{name:'张三'}};
getLog(data,'child.name');//张三(data[child][name])

代码整合

  • 准备模板
  • 渲染工作
  • 将模板结合数据得到html加载到页面中
  • 编译 将模板和数据结合得到正真的DOM元素
  • 将DOM元素加载到页面中
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>
<body>
  <div id="root" title="aaa">
    <p>{{name}}:{{message.info}}</p>
    <p>{{name}}</p>
    <p>{{message.info}}</p>
  </div>
  <script>
    console.log(document.getElementById("root").childNodes);
    // 正则
    var rz = /\{\{(.+?)\}\}/g;
    function compiler ( tmp , data){
      var txtNodes = tmp.childNodes;//获取root节点的子节点
      // 判断至子节点是否为文本节点( nodeType )
      for (let i = 0;i < txtNodes.length;i++){
        // 文本节点
        if( txtNodes[i].nodeType == 3){
          // 通过正则判断里面是否有 {{}}
          let txt = txtNodes[i].nodeValue;//nodeValue属性只有再文本节点中才有意义
          // replace 使用正则匹配一次 函数就会被调用一次
          // 函数的第0个参数 表示匹配到的内容
          // 函数的第n个参数 便是正则的 第n组
          txt = txt.replace(rz , function ( _, g ){
            let key = g.trim();
            let getLogs = getLog( key );
            let val = getLogs(data);
            return val;
          })
          // txt 现在和 DOM 元素没有联系,需通过数据赋值
          txtNodes[i].nodeValue = txt; 
        }else if(txtNodes[i].nodeType == 1){
          // 元素节点
          compiler ( txtNodes[i] , data)
        }
      }
    }

    function JGvue( options ){
      this._data = options.data;
      this._el = options.el;

      // 准备模板
      this.$el = this._templateDOM = document.querySelector( this._el );
      this._parent = this._templateDOM.parentNode;
      // 渲染工作
      this.render();
    }
    // 将模板结合数据得到html加到页面中
    JGvue.prototype.render = function () {
      this.compiler();
    }

    // 编译 将模板和数据结合得到正真的dom元素
    JGvue.prototype.compiler = function () {
      let realHtmlDom = this._templateDOM.cloneNode( true );
      compiler (realHtmlDom , this._data);
      this.update(realHtmlDom);
    }

    // 将dom元素放到页面中
    JGvue.prototype.update = function (real){
      this._parent.replaceChild(real , this.$el)
    }

    // 2、处理只考虑了单属性 ({{name}}),未考虑多层级关系({{child.name}})问题
    //vue的实现方式
    function getLog( path ){
      let arr = path.split('.');
      return function getValue(obj){
        let res = obj;
        let prop;
        // arr.shift()取出数组的第一个值
        while( prop = arr.shift() ){
          res = res[ prop ];
        }
        return res;
      }
    }
    let app = new JGvue({
      el:'#root',
      data:{
        name:'jim',
        message:{info:'info'}
      }
    })
  </script>
</body>
</html>

生成虚拟DOM

1、虚拟DOM中需包含的内容有tag元素名称、attr属性键值对、value文本内容、type节点类型、children子元素节点(子元素节点也包含前4项内容)等等【实例中只包含上述几项内容】

2、生成虚拟DOM算法【实例中只判断文本节点、元素节点】

  • 判断节点类型(xx.nodeType:值为1时为元素节点;为3是为文本节点)
  • 为元素节点时,首先获取节点名称(el.nodeName)、节点属性(el.attributes)、元素子节点(el.childNodes)。但是通过attributes获取的节点属性为伪数组,需将伪数组通过循环转为数组对象,实例化VNode类。最后通过递归的方式以及VNode中getData方法,实现对子节点生成虚拟DOM并插入VNode中children数组中
  • 为文本节点时,获取文本节点的内容(el.nodeValue)然后实例VNode类
    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);
      }
    }
    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;
    }