# 初始化渲染Watcher
// src/init.js
import { compileToFunctions } from './compiler/index';
import { mountComponent } from './lifeCycle';
import { initState } from './state';
export function initMixin(Vue) {
Vue.prototype._init = function(options) {
const vm = this;
vm.$options = options; // 实例上有个属性$options表示的是用户传入的属性,可以通过vm.$options获取到用户传入的所有属性
// 初始化状态
initState(vm);
// 有el属性的话,说明数据可以挂在到页面上
if (vm.$options.el) {
vm.$mount(vm.$options.el);
}
};
Vue.prototype.$mount = function(el) {
const vm = this;
const options = vm.$options;
el = document.querySelector(el);
// 如果有render就直接用render,没有render,看看有没有template属性,如果也没有template属性的话,就直接找外部模板
// 如果没有render方法
if (!options.render) {
let template = options.template;
// 如果没有模板但是有el
if (!template && el) {
template = el.outerHTML;
}
// 将模板编译成render函数
const render = compileToFunctions(template);
options.render = render;
console.log('render', render);
}
mountComponent(vm, el); // 组件挂载
};
}
// src/lifeCycle.js
import Watcher from "./observer/watcher";
export function lifecycleMixin(Vue) {
Vue.prototype._update = function(vnode) {
console.log('_udpate', vnode)
};
}
export function mountComponent(vm, el) {
// 默认 vue 通过 watcher 来渲染,这个watcher可以叫做渲染watcher,每个组件都有一个渲染watcher
vm.$el = el;
let updateComponent = () => {
// 将虚拟节点 渲染到页面上
// vm._render() 返回虚拟节点
// vm._update() 将虚拟节点转换成真实节点
vm._update(vm._render());
};
new Watcher(vm, updateComponent, () => {
// 回调函数
}, true); // true 表示这是一个渲染watcher
}
// src/observer/watcher.js
// id用于标识不同组件不同的watcher
let id = 0;
class Watcher {
constructor(vm, exprOrFn, cb, options) {
this.vm = vm;
this.exprOrFn = exprOrFn;
if (typeof exprOrFn == 'function') {
this.getter = exprOrFn;
}
this.cb = cb;
this.options = options;
// 每创建一个watcher就让id自增
this.id = id++;
this.get();
}
get() {
this.getter();
}
}
export default Watcher;
# 生成虚拟dom
// src/render.js
import { createElement, createTextNode } from "./vdom/index";
export function renderMixin(Vue) {
Vue.prototype._v = function(text) {
// 创建文本
return createTextNode(this, text);
};
Vue.prototype._c = function() {
// 创建元素
return createElement(this, ...arguments);
};
Vue.prototype._s = function(val) {
// 转化成字符串
return val == null ? '' : typeof val === 'object' ? JSON.stringify(val) : val;
};
Vue.prototype._render = function() {
console.log('_render');
const vm = this;
const render = vm.$options.render; // 获取编译后的render方法
// 调用render方法生成vnode,在调用时自动对变量求值
const vnode = render.call(vm);
return vnode;
};
}
创建虚拟节点
// src/vdom/index.js
/*
虚拟dom可以随意添加属性,ast是针对语法解析出来的,不能随意添加属性
*/
export function createTextNode(vm, text) {
return vnode(vm, undefined, undefined, undefined, undefined, text);
}
export function createElement(vm, tag, data = {}, ...children) {
// 如果是列表元素需要添加key属性,这里对key进行处理
let key = data.key;
if (key) {
delete data.key;
}
return vnode(vm, tag, data, key, children);
}
function vnode(vm, tag, data, key, children, text) {
return {
vm,
tag,
data,
key,
children,
text
};
}
<div id="app" a=1 b=2 style="color:red;font-size:12px;">
<span style="color:red;">{{name}} aa {{age}} haha<a>hello</a></span>
</div>
上面的代码生成的 vnode 大体如下:
# 生成真实DOM元素
// src/init.js
import { compileToFunctions } from './compiler/index';
import { mountComponent } from './lifeCycle';
import { initState } from './state';
export function initMixin(Vue) {
Vue.prototype._init = function(options) {
const vm = this;
vm.$options = options; // 实例上有个属性$options表示的是用户传入的属性,可以通过vm.$options获取到用户传入的所有属性
// 初始化状态
initState(vm);
// 有el属性的话,说明数据可以挂在到页面上
if (vm.$options.el) {
vm.$mount(vm.$options.el);
}
};
Vue.prototype.$mount = function(el) {
const vm = this;
const options = vm.$options;
el = document.querySelector(el);
vm.$options.el = el;
// 如果有render就直接用render,没有render,看看有没有template属性,如果也没有template属性的话,就直接找外部模板
// 如果没有render方法
if (!options.render) {
let template = options.template;
// 如果没有模板但是有el
if (!template && el) {
template = el.outerHTML;
}
// 将模板编译成render函数
const render = compileToFunctions(template);
options.render = render;
console.log('render', render);
}
mountComponent(vm, el); // 组件挂载
};
}
// src/lifeCycle.js
import { patch } from './observer/patch';
import Watcher from './observer/watcher';
export function lifecycleMixin(Vue) {
Vue.prototype._update = function(vnode) {
console.log('_udpate', vnode);
// 将虚拟节点转换成真实dom
const vm = this;
// 首次渲染需要用虚拟节点更新真实dom
vm.$el = patch(vm.$options.el, vnode);
};
}
export function mountComponent(vm, el) {
// 默认 vue 通过 watcher 来渲染,这个watcher可以叫做渲染watcher,每个组件都有一个渲染watcher
vm.$el = el;
let updateComponent = () => {
// 将虚拟节点 渲染到页面上
// vm._render() 返回虚拟节点
// vm._update() 将虚拟节点转换成真实节点
vm._update(vm._render());
};
new Watcher(
vm,
updateComponent,
() => {
// 回调函数
},
true
); // true 表示这是一个渲染watcher
}
// src/observer/patch.js
/*
oldVnode 第一次是一个真实节点
*/
export function patch(oldVnode, vnode) {
// 是否是真实的 DOM 节点
const isRealElement = oldVnode.nodeType;
if (isRealElement) { // 如果oldVnode是一个真实DOM,表示是初次渲染
const oldElm = oldVnode;
const parentElm = oldElm.parentNode;
// 根据虚拟节点创建真实节点
let el = createElm(vnode);
// 将创建的节点插入到原有节点下面
parentElm.insertBefore(el, oldElm.nextSibling);
parentElm.removeChild(oldVnode);
// 把新创建的 el 替换成 vm.$el
return el;
}
}
// 根据虚拟节点创建真实节点
function createElm(vnode) {
let { tag, children, key, data, text } = vnode;
// 如果是标签,有可能是组件,这里忽略
if (typeof tag === 'string') {
vnode.el = document.createElement(tag);
// 更新属性
updateProperties(vnode);
// 如果有子节点,需要进行递归操作
children.forEach((child) => {
return vnode.el.appendChild(createElm(child));
});
} else {
// 处理文本节点
vnode.el = document.createTextNode(text);
}
return vnode.el;
}
// 更新节点属性
function updateProperties(vnode) {
let newProps = vnode.data || {}; // 获取当前老节点中的属性
let el = vnode.el; // 当前的真实节点
for (let key in newProps) {
// 处理样式
if (key === 'style') {
// 设置样式
for (let styleName in newProps.style) {
el.style[styleName] = newProps.style[styleName];
}
} else if (key === 'class') {
// 设置类名
el.className = newProps.class;
} else {
// 给这个元素添加属性 值就是对应的值
el.setAttribute(key, newProps[key]);
}
}
}