1、前言
初始化挂载的实现:根据render方法产生虚拟节点,在将虚拟节点变成真实节点 插入到页面中即可;
Vue.prototype.$mount = function(el){
const vm = this;
el = document.querySelector(el);
vm.$el = el;
...
mountComponent(vm,el)// 组件挂载流程
};
function mountComponent(vm){
vm._update(vm._render());
}
2、调用vm._render() 生成一个虚拟dom
将模板编译的的render函数执行,生成一个虚拟dom ;之前的编译render函数返回的code 类似于这样(_c("div",{id:"app"},[_c("span",{class:"a"},[_v("name")]),_v(_s(name))]))
虚拟dom的形式
function vnode(vm, tag, props, children, text, key) {
return {
vm,
tag,
props,
children,
text,
key
}
}
2.1、_render函数
Vue.prototype._render = function(){ // 调用编译后的render方法,生成虚拟节点
const vm = this;
let {render} = vm.$options;
let vnode = render.call(vm); // 这里会进行取值操作 触发了get方法 仅需取值 (依赖收集)
return vnode
}
2.2 render函数中_c、_v、_s函数
// _c 创建标签节点
Vue.prototype._c = function(){ // _c('div',undefoined,[])
return createElement(this,...arguments)
}
//创建文本节点
Vue.prototype._v = function(text){ // _v(字符串 + name + 'xxx')
return createTextElement(this,text)
}
//变量
Vue.prototype._s = function(val){ // _s(name)
if(typeof val === 'object') return JSON.stringify(val);
return val;
}
//创建节点
function createElement(vm,tag, props = {}, children) {
return vnode(vm, tag, props, children,undefined, props.key)
}
//创建文本
function createTextElement(vm,text) {
return vnode(vm, undefined, undefined, undefined, text)
}
//虚拟dom
function vnode(vm, tag, props, children, text, key) {
return {
vm,
tag,
props,
children,
text,
key
}
}
3、调用_update()方法,实现虚拟dom变成真实dom完成初始化挂载
Vue.prototype._update = function (vnode){ // 虚拟dom变成真实dom进行渲染
const vm = this;
const el = vm.$el; //<div id="app"></div>
patch(el,vnode); // 渲染 传入一个真实的dom 和 vnode
3.1 patch 方法,初始化渲染时,先根据虚拟节点创建一个真实节点,将此创建节点插入到页面中再将老节点删除 ,完成挂载
function patch(oldVnode, vnode) {
if (oldVnode.nodeType === 1) { //nodeType dom的原生属性 1 为元素 3为文本
// 初始化渲染操作
// 根据虚拟节点创造真实节点, 先根据虚拟节点创建一个真实节点,将节点插入到页面中在将老节点删除
const parentElm = oldVnode.parentNode; // 获取父元素
const elm = createElm(vnode)
parentElm.insertBefore(elm, oldVnode.nextSibling)
parentElm.removeChild(oldVnode);
return elm;
} else {
//diff 算法...
}
}
function createElm(vnode) {
const { tag, props, children, text } = vnode;
if (typeof tag == 'string') {
vnode.el = document.createElement(tag); // 把创建的真实dom和虚拟dom映射在一起方便后续更新复用
updateProperties(vnode); //解析vnode的属性
children && children.forEach(child => {
vnode.el.appendChild(createElm(child))
});
} else {
vnode.el = document.createTextNode(text);
}
return vnode.el;
}
// 解析vnode的data属性
function updateProperties(vnode) {
let newProps = vnode.props || {};
let newStyle = newProps.style || {};
let el = vnode.el; //真实节点
for (let key in newProps) {
// style需要特殊处理下
if (key === "style") {
for (let styleName in newStyle) {
el.style[styleName] = newStyle[styleName]; }
} else if (key === "class") {
el.className = newProps.class;
} else {
// 给这个元素添加属性 值就是对应的值
el.setAttribute(key, newProps[key]);
}
}
}