本篇主要讲之前的代码的知识点,进行整合
这里我们用combine替换之前的compiler
combine用来合并数据与带有{{}}虚拟的Dom
compiler用来合并数据与带有{{}}真实的Dom
<div id="root">
<p>{{ name.firstName }}</p>
</div>
// ==========================combine函数 合并数据与带有{{}}虚拟的Domdom===================================s
//解析data.xxx.xx
/**
*@params obj:Object data:{nama:{firstName:'小',lastName:'陈'}}
*@params path:String 'xxx.xxx'
*@return res:any
*/
function getValueByPath(data, path) {
let paths = path.split(".");
//[name, firstName];
let res = data;
//循环查找
for (let index = 0; index < paths.length; index++) {
res = res[paths[index]];
}
// res === '小'
return res;
}
//正则,用来匹配{{}}
let r = /\{\{(.+?)\}\}/g;
// 带坑的vnode和数据 合并 生成带数据vnode
/**
*@params vnode:VNode (带有{{}}的虚拟dom)
*@params data:Object(数据源)
*@return _vnode:VNode(带有数据的虚拟dom)
*/
function combine(vnode, data) {
let {
tag: _tag,
data: _data,
value: _value,
type: _type,
children: _children,
} = vnode;
let _vnode = null;
if (_type === 3) {
//文本节点 判断 是否有{{}}
_value = _value.replace(r, (_, g) => {
let key = g.trim();
let value = getValueByPath(data, key);
return value;
});
_vnode = new VNode(_tag, _data, _value, _type);
} else if (_type === 1) {
_vnode = new VNode(_tag, _data, _value, _type);
_children.forEach((element) => {
_vnode.appendChild(combine(element, data));
});
}
return _vnode;
}
/*
// ==========================paraseVnode函数 虚拟dom转真实dom===================================
*@params tag:String 'div||span||h1'
*@params data:Object '{id:'root,class:'theme'...}'
*@params value:String 'nodeValue || undefined'
*@params type:Number 'nodeType=1 || 2 ||3 '
*@params children:Array<Vnode>
@return dom:HtmlElement
*/
function createElement(tag, data, value, type, children) {
let dom; //定义储存dom变量
// 元素节点
if (type === 1) {
//创建元素
dom = document.createElement(tag);
//添加属性
for (const key in data) {
if (data.hasOwnProperty.call(data, key)) {
dom.setAttribute(key, data[key]);
}
}
// 存在子节点遍历循环递归
if (children) {
for (let index = 0; index < children.length; index++) {
let nodeTag = children[index].tag;
let nodeData = children[index].data;
let nodeValue = children[index].value;
let nodeType = children[index].type;
let nodeChildren = children[index].children;
dom.appendChild(
createElement(
nodeTag,
nodeData,
nodeValue,
nodeType,
nodeChildren
)
);
}
}
}
// 文本节点,创建文本节点
else if (type === 3) {
dom = document.createTextNode(value);
}
return dom;
}
/*
*@params vnode:Object
@return dom:HtmlElement
*/
function paraseVnode(vnode) {
return createElement(
vnode.tag,
vnode.data,
vnode.value,
vnode.type,
vnode.children
);
}
// ==========================getVNode函数 真实dom转虚拟dom===================================
/*
*@params tag:String 'div||span||h1'
*@params data:Object '{id:'root,class:'theme'...}'
*@params value:String 'nodeValue || undefined'
*@params type:Number 'nodeType=1 || 2 ||3 '
*/
class VNode {
constructor(tag, data, value, type) {
this.tag = tag && tag.toLocaleLowerCase();
this.data = data;
this.value = value;
this.type = type;
this.children = []; //子节点数组,默认[]
}
// 添加子节点
appendChild(childrenNode) {
this.children.push(childrenNode);
}
}
/*
*@params realDom:HtmlElement '真实的DOM对象'
*@return vNode:Object '虚拟的DOM对象'
*/
function getVNode(realDom) {
let nodeType = realDom.nodeType; //节点类型
let _vode = null;
// 元素节点
if (nodeType === 1) {
let tag = realDom.nodeName; //标签名
let attributes = realDom.attributes; //对象属性
let childNodes = realDom.childNodes; //子节点
// 获取属性对象
let data = {};
for (let i = 0; i < attributes.length; i++) {
let key = attributes[i].nodeName;
let value = attributes[i].value;
data[key] = value;
}
// 生成虚拟dom对象
_vode = new VNode(tag, data, undefined, nodeType);
//如果有子节点,添加子节点,递归遍历 ===本节难点!!!===
for (let i = 0; i < childNodes.length; i++) {
_vode.appendChild(getVNode(childNodes[i]));
}
}
// 文本节点
else if (nodeType === 3) {
_vode = new VNode(undefined, undefined, realDom.nodeValue, nodeType);
}
return _vode; //虚拟dom
}
// ==========================Vue构造函数===================================
function Vue(options) {
// 内部数据使用_ 开头 ,只读数据$开头
this._data = options.data;
this._el = options.el;
//准备工作(模板)
this.$el = this._templateNode = document.querySelector(this._el);//获取挂载的对象
this._parent = this._templateNode.parentNode;//父节点
//挂载
this.mount();
}
//挂载
Vue.prototype.mount = function () {
this.render = this.createRenderFn(); //生成带有data数据的vnode
//挂载dom
this.mountComponet();
};
// 函数颗粒化,缓存带有{{}}的vnode
Vue.prototype.createRenderFn = function () {
//带坑的虚拟dom
let ast = getVNode(this.$el);
return function () {
// 生成带数据vnode
let combineData = combine(ast, this._data);
return combineData;
};
};
//这里就是以后当数据发生变化watcher来进行调用 更新组件vue diff算法就在这里
Vue.prototype.mountComponet = function () {
this.updata(this.render());
};
// 更新dom 到页面
Vue.prototype.updata = function (vnode) {
//将带有数据的vnode转成真实的dom
let realDom = paraseVnode(vnode);
//这里我们直接操作dom替换了,后面再细讨论
this.$el.parentNode.replaceChild(realDom, this.$el);
};
// 创建Vue的实例
const vm = new Vue({
el: "#root",
data: {
name: {
firstName: "小",
lastName: "陈",
},
},
});