vue源码系列4之虚拟dom和真实dom
compiler / index.js
import {
generate
} from "./genrate.js";
import {
parserHTML
} from "./parser.js";
export function compileToFunction(template) {
let root = parserHTML(template);
let code = generate(root);
let render = new Function(`with(this){return ${code}}`)
return render;
}
init.js
- 在初始化函数之中,调用
compileToFunction(template) 函数,把模板变成渲染函数,返回渲染函数 - 之后的话,就是调用
render函数 渲染为真实dom,替换掉页面的内容! - 替换掉页面的前提:需要借助组件的挂载
- 并且挂载在Vue实例对象身上
vm.$options ,为之后访问vm实例能获取到 render函数 - 有了renden函数之后,就是考虑渲染真实dom了,替换掉页面的内容
- 就是通过
mountComponent(vm, el); 把组件的实例对象vm挂载在el身上;
import {
compileToFunction
} from "./compiler/index.js";
import { mountComponent } from "./lifecycle.js";
import {
initSate
} from "./state";
export function initMixin(Vue) {
Vue.prototype._init = function (options) {
const vm = this;
vm.$options = options;
initSate(vm);
if (vm.$options.el) {
vm.$mount(vm.$options.el);
}
}
Vue.prototype.$mount = function (el) {
let vm = this;
let options = vm.$options;
el = document.querySelector(el);
if (!options.render) {
let template = options.template;
if (!template && el) {
template = el.outerHTML;
let render = compileToFunction(template);
options.render = render;
}
}
mountComponent(vm, el);
}
}
lifecycle.js
- 关于组件的挂载,这时候就需要
updataComponent函数了 - 组件挂载的时候,先调用一次此函数 updataComponent
- 数据一发生变化就调用此函数 updataComponent
- 其中
updataComponent函数的两大作用
- 01:调用render函数,生成虚拟dom
- 02:用虚拟dom 生成真实dom
vm._updata(vm._render())
- 也就是vm实例上原型的 更新虚拟dom方法,用于更新render渲染函数
- 其中原型之中的
Vue.prototype._updata函数 ,则目的:为了根据虚拟dom创建真实dom - 其根本需要在
index.js 之中传入Vue实例对象 - 之后的话,就拿到虚拟dom,通过patch方法 把虚拟dom转化为真实dom
- 注意点:就是vm._render()函数定义于 render.js之内,创建的一个render渲染函数,并且挂载于vm实例身上
import { patch } from "./vnode/patch";
export function leftcycleMixin(Vue) {
Vue.prototype._updata = function (vnode) {
console.log("updata 拿到虚拟dom", vnode);
const vm = this;
patch(vm.$el, vnode);
}
}
export function mountComponent(vm, el) {
let updataComponent = () => {
vm._updata(vm._render());
}
updataComponent();
}
index.js
import {
initMixin
} from "./init";
import {
leftcycleMixin
} from "./lifecycle";
import { renderMixin } from "./render";
function Vue(options) {
this._init(options);
}
initMixin(Vue)
renderMixin(Vue);
leftcycleMixin(Vue);
export default Vue;
render.js
- 创建的render渲染函数,并且挂载在vm实例身上
- 其中
vm.$options.render 就是我们解析出来的render方法,同时也有可能是用户书写的! - 针对render函数之中的创建 标签 文本 等虚拟节点,特此在Vue原型内定义了
_c 、_v 、_s 等原型方法,皆可用于之后的创建虚拟dom!
import {
createElement,
createTextElement
} from "./vnode/index.js";
export function renderMixin(Vue) {
Vue.prototype._c = function (tag, data, ...children) {
return createElement(this, ...arguments)
}
Vue.prototype._v = function (text) {
return createTextElement(this, text);
}
VisualViewport.prototype._s = function (val) {
if (typeof val == "object") return JSON.stringify(val);
return val;
}
Vue.prototype._render = function () {
const vm = this;
const render = vm.$options.render;
let vnode = render.call(vm);
return vnode;
}
}
vnode / index.js
export function createElement(vm, tag, data = {}, ...children) {
return vnode(vm, tag, data, data.key, children, undefined)
}
export function createTextElement(vm, text) {
return vnode(vm, undefined, undefined, undefined, undefined, text)
}
function vnode(vm, tag, data, key, children, text) {
return {
vm,
tag,
data,
key,
children,
text
}
}
vnode / patch.js
- 比较前后dom的差异,监听数据的变化,进行重写渲染dom节点(初始化的时候,也会渲染dom节点)
patch函数 把虚拟dom转化为真实dom
patch(vm.$el, vnode); 在挂载的时候 -> liftcycle.js生命周期.js文件内,就是挂载时候,传入当前挂载的模板el,还有虚拟节点; - 作用:就是把旧节点,替换为新的节点
- 根据传入的旧节点 与 虚拟节点 进行逻辑处理
- 首先先寻找到当前旧节点的父元素,然后移除掉当前当前父元素下的所有节点(标签、文本、空格等)
- 之后,通过动态创建标签的形式,以虚拟dom上的节点,区分不同的元素,标签、文本等来创建出对应的标签、还有子元素、还有文本节点等
- 紧接着,就是插入到当前旧节点之前
- 最后删除掉旧节点,也就实现 旧 -> 新 (根据虚拟dom来渲染出真实的dom节点)
export function patch(oldVnode, vnode) {
if (oldVnode.nodeType == 1) {
const parentElm = oldVnode.parentNode;
let elm = createElm(vnode);
parentElm.insertBefore(elm, oldVnode.nextSibling);
parentElm.removeChild(oldVnode)
}
}
function createElm(vnode) {
let {
tag,
data,
children,
text,
vm
} = vnode;
if (typeof vnode.tag === "string") {
vnode.el = document.createElement(tag);
children.forEach(child => {
vnode.el.appendChild(createElm(child))
});
} else {
vnode.el = document.createTextNode(text)
}
return vnode.el;
}
|