2020年11月17日
数据驱动模型(2)
在数据驱动模型(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; }