Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Vue 2.x 编译模板(不是虚拟DOM,为了后面的收集依赖,暂时写的) #35

Open
lovelmh13 opened this issue Feb 13, 2020 · 0 comments

Comments

@lovelmh13
Copy link
Owner

lovelmh13 commented Feb 13, 2020

依然承接上一次的代码

上次说完了数据的响应,这次该到依赖收集了。

但是,在依赖收集之前,先暂时搞定一下模板渲染,供后面依赖收集使用。

1. 首先先解决挂在到el上的问题,执行$mount函数

function Vue(options) {
	this._init(options);
}

Vue.prototype._init = function(options) {
	let vm = this; // 把this叫成vm,方便表示this是实例,方便看,不用多想
	vm.$options = options;

	// MVVM原理,数据响应式,需要重新初始化数据
	initState(vm);

        // 执行$mount函数
	if (vm.$options.el) {
		vm.$mount()
	}
};

function query (el) {
	if (typeof el === 'string') {	// 如果是字符串,就返回对应的dom元素
		return document.querySelector(el);
	}
	return el;	// 如果传进来的是dom元素,直接返回传进来的dom元素
}

Vue.prototype.$mount = function() {
	let vm = this;
	let el = this.$options.el;	// 1. 获取元素
	el = vm.$el = query(el);	// vm.$el就是当前挂在的dom元素

	// 2. 该渲染页面了
	// 渲染是通过watcher来渲染的
	// vue 2.0 组件级别的更新, new Vue 产生一个组件

	let updateComponent = () => {	// 用来更新组件、渲染的逻辑
		this._update();
	}
	
        // 3. 执行Watcher,new个实例
	new Watcher(vm, updateComponent);
}

2. 我们先初始化一个Watcher对象

let id = 0;	// 用来区分是哪一个Watcher
class Watcher {
	/**
	 * 
	 * @param {*} vm 当前组件的实例
	 * @param {*} exprOrFn 更新视图或者渲染视图。用户可能传一个表达式或者一个函数
	 * @param {*} cb 用户传的回调函数 vm.$watch
	 * @param {*} opts 其他参数
	 */
	constructor(vm, exprOrFn, cb=() => {}, opts={}) {
		this.vm = vm;
		this.exprOrFn = exprOrFn;
		this.cb = cb;
		this.opts = opts;
		this.id = id++; // 每次新创建一个watcher实例,就给定一个id号

		if (typeof exprOrFn === 'function') {	// 如果是函数,那么就把exprOrFn赋值给this.getter
			this.getter = exprOrFn;
		}
		this.get();	// 默认创建一个watcher  会调用自身的get方法
	}

	get() {
		this.getter();
	}
}

export default Watcher

3. 渲染模板,把数据渲染到页面上。这里用文档碎片参实代替虚拟Dom,不是实际vue用的渲染。

看到Watcher,我们知道,需要执行一个get(),也就是this.getter();,也就是exprOrFn;,就是最开始我们new Watcher的时候传进来的updateComponent

updateComponent需要进行_update来更新组件,也就是更新{{}}这个。

Vue.prototype._update = function () {
	let vm = this;
	let el = vm.$el;

	// 1. 模拟渲染模板,真正的会用虚拟dom
	let node = document.createDocumentFragment(); // 创建文档碎片,比一个一个放dom性能要好
	let firstChild;
	while(firstChild = el.firstChild) { // 每拿到第一个元素就将这个元素放入到文档碎片中,firstChild可以获取的文本节点,这个是关键。而firstElementChild不能获取文本节点,所以不要用
		node.appendChild(firstChild);	// appendChild有移动节点的功能,原来的节点会被删除,都放到文档碎片 document-fragment里面去了。所以页面就会空白。我们替换好文本以后,再给添加回去就行
	}

	// 2. 对文本进行替换,需要匹配{{}}的方式来进行替换
	compiler(node, vm);
	
	// 3.把替换好的节点再给插回到el下面。这样用文档碎片,只需要渲染一次
	// 不然的话,每一次编译{{}},都需要渲染,开销太大
	el.appendChild(node);
}

看一下compiler是怎么进行模板替换的

function compiler (node, vm) {
	let childNodes = node.childNodes;
	// 把类数组,转成数组
	[...childNodes].forEach(child => {
		if (child.nodeType == 1) {	// 1代表元素节点  3代表文本节点
			compiler(child, vm)	// 2. 递归,如果是元素节点,就再去遍历后面的child
		} else if (child.nodeType == 3) {
			util.compilerText(child, vm)	// 1. 把文本进行替换(文本可能是普通的文字,也可以能是{{xxx}},替换掉{xxx}}就是我们的目标)
		}
	})
}

util.compilerText方法就是用来把找出文本里的{{xxx}}字符,并且换成我们更新过后的内容
注意一下在返回我们要的内容的时候,有个问题主要注意,在代码中标出了。

// ?: 匹配不捕获,不捕获当前的分组
// . 任意字符
// + 至少一个
// ? 尽可能少匹配
const defaultREG = /\{\{((?:.|\r\n)+?)\}\}/g; // 匹配{{}}用
const util = {
	getValue(vm, expr) {
		let keys = expr.split('.');// ['school', 'name']
		// 这里注意,巧用了reduce累加这个api,这是一个神奇的api,他具备迭代的功能。而且不用担心数据里只有一个元素的问题
		return keys.reduce((memo, current) => {
			memo = memo[current];
			return memo;
			/* 
				假如 expr 是'school.name',那 keys 就是 ['school', 'name']
				1. 第一次传进来的是vm, memo[current]是vm.schoole
				2. 我们把vm.schoole赋给memo,下一次就会取vm.schoole.name了
				3. 把schoole.name返回回去,这就是我们想要的值
			*/
		}, vm) // vm是第一次调用callback函数时的第一个参数的值,也就是第一次的memo
	},
	compilerText(node, vm) {	// 编译文本,代替{{}}
		node.textContent = node.textContent.replace(defaultREG, function (...args) {
			// 注意,这里有可能模板上会写{{school.name}},我们如果直接返回 vm[args[1]], 就相当于是vm[school.name],这样的写法是错的,所以需要处理一下,写成vm[school][name],getValue方法就是用来处理的
			return util.getValue(vm, args[1]);
		})
	}
}
@lovelmh13 lovelmh13 changed the title Vue 2.x 渲染原理 watcher依赖收集 Vue 2.x watcher依赖收集 Feb 13, 2020
@lovelmh13 lovelmh13 changed the title Vue 2.x watcher依赖收集 Vue 2.x 渲染模板(不是虚拟DOM),为了后面的收集依赖,暂时写的 Feb 13, 2020
@lovelmh13 lovelmh13 changed the title Vue 2.x 渲染模板(不是虚拟DOM),为了后面的收集依赖,暂时写的 Vue 2.x 编译模板(不是虚拟DOM,为了后面的收集依赖,暂时写的) Feb 13, 2020
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant