# 初始化渲染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]);
		}
	}
}