3、template、data、methods、v-if、v-show、事件解析

204 阅读2分钟

一、vue的模板template:

模板template的组成:vue的模板都是基于HTML字符串、vue特性、表达式/属性/指令。

vue模板到真实dom的过程:

1、vue中的模板html需要经过vue的html解析器转成AST树;

2、AST树转虚拟dom;

3、虚拟dom解析成真实dom,最后挂载到html;

二、data 函数

1、data函数返回的对象,变成响应式的过程:

1、Vue在创建实例的过程中调用data函数返回数据对象;

2、给对象通过响应式包装后得到代理对象

3、把代理对象存储在实例的$data上 ;

4、实例可以直接越过$data,通过get set 存/取值器,访问属性,。

const App = {
	data() {
		return {
			qige: 'hello world'
		}
	},
	template: `
	<div >
	{{key}}
	</div>
	`
}

//Vue3
const { createApp } = Vue
var vm=createApp(App).mount('#app');
console.log(vm);

//Vue2
var vm =new Vue({
	render: h => h(App)
}).$mount('#app')
console.log(vm);

vue3

image.png

vue2

image.png

2、data为什么必须是一个函数?

不做深度代理实现代码,:

function MyVue(options) {
	this.$data = options.data();
	var _this = this;

	for (var key in this.$data) {
		(function(key) {
			Object.defineProperty(_this, key, {
				get: function() {
					return _this.$data[key]
				},
				set: function(newValue) {
					_this.$data[key] = newValue
				}
			})
		})(key);
	}
}
function data() {
	return {
		a: 1,
		b: 2
	}
}

var myVm = new MyVue({data});

console.log(myVm);
console.log("a::",myVm.a);
console.log("b::",,myVm.b);
myVm.$data.a=10
myVm.b=15
console.log("a::",myVm.a);
console.log("b::",,myVm.b);

image.png

代码逻辑说明:

1、MyVue构造函数声明一个$data保存data函数返回值;

2、遍历data把里面的每一个属性设置成响应式,通过getset方法进行拦截data把里面的每一个属性设置成响应式,通过**get、set方法进行拦截data**里面属性的操作;

3、之所以能够通过myVm.a或者myVm.data访问属性,主要是getset内部操作的是data访问属性,主要是get、set内部操作的是data对象;

//构造函数里面的this.$data = options.data();改为以下
this.$data = options.data;

//data改为一个对象
var data = {
	qige: 1,
	wuzai: 2
}
var myVm1 = new MyVue({ data });
var myVm2 = new MyVue({ data });
myVm2.qige = 15
console.log("myVm1::", myVm1);
console.log("myVm2::", myVm2);

image.png

那data为什么必须是一个函数?

通过上图看出;myVm2.qige = 15把qige属性的值改了,myVm1里面的属性qige的值也该了,因为他们的$data指向的是同一个引用

data是一个函数可以保证每一个实例的$data都指向不同的引用。不会导致组件实例的数据会被其他组件实例修改。

当然data如果是一个对象,也可以通过深度克隆保证组件实例data的唯一性。但是从性能的角度看,深度克隆需要通过递归,data定义为函数直接。

三、methods对象

目的:向组件实例添加方法;

1.Vue创建实例时,遍历methods对象,给当前组件实例添加methods对象的方法,并且绑定方法内部的this指向为当前实例;

2、保证在事件监听时,回调始终指向当前组件实例;

3、方法要避免使用箭头函数,箭头函数会阻止Vue正确绑定组件实例this。

function MyVue(options) {
	this.$data = options.data();
	this._methods = options.methods;
	
	function _init(){
		this.initData();
		this.initMethods();
	}
	MyVue.prototype.initData = function() {
		var _this = this;
		for (var key in _this.$data) {
			(function(key) {
				Object.defineProperty(_this, key, {
					get: function() {
						return _this.$data[key]
					},
					set: function(newValue) {
						_this.$data[key] = newValue
					}
				})
			})(key);
		}
	}
	
	MyVue.prototype.initMethods = function() {
		for (var key in options.methods) {
			this[key] = options.methods[key].bind(this);
		}
	}
	_init.call(this)
}


const App = {
	data() {
		return {
			qige: 'hello world'
		}
	},
	template: `
	<div >
	{{qige}}
	</div>
	`,
	methods: {
		test(a) {
			console.log(a);
		}
	}
}

var myVm1 = new MyVue(App);
myVm1.test(myVm1.qige)

image.png

四:v-if、v-show、事件解析

const App = {
	el: "#app",
	data() {
		return {
			qige: 'hello world',
			wuzai: 'hello wuzai',
			myif: false,
			myshow: false
		}
	},
	beforeCreate(){
		console.log('beforeCreate');
	},
	created(){
		console.log('created');
	},
	beforeMounte(){
		console.log('beforeMounte');
	},
	mounted(){
		console.log('mounted');
	},
	template: `
		<p v-if="myif">{{qige}}</p>
		<p v-show="myshow">{{wuzai}}</p>
		<button @click='showQige'>显示七鸽</button>
		<button  @click='showWuzai'>显示舞载</button>
	`,
	methods: {
		showQige() {
			this.myif = !this.myif;
		},
		showWuzai() {
			this.myshow = !this.myshow;
			this.print()
		},
		print() {
			console.log('print');
		}
	}
}
var myVue = (function() {
	function MyVue(options) {
		var recyles={
			beforeCreate:options.beforeCreate.bind(this),
			created:options.created.bind(this),
			beforeMount:options.beforeMounte.bind(this),
			mounted:options.mounted.bind(this),
			
		}
		recyles.beforeCreate();
		this.$el = document.querySelector(options.el);
		this.$data = options.data();
		recyles.created()
		this._init(this, options.template, options.methods,recyles);
	}

	MyVue.prototype._init = function(vm, template, methods,recyles) {
			var showPool = new Map(),
				eventPool = new Map(),
				container = document.createElement('div');
				container.innerHTML = template;
			initData(vm, showPool);
			initMethods(vm, methods);
			initPool(container, methods, showPool, eventPool);
			bindEvent(vm, eventPool);
			render(vm, showPool, container,recyles);
		}

		function initMethods(vm, methods) {
			for (var key in methods) {
				vm[key] = methods[key].bind(vm);
			}
		}

		function initData(vm, showPool) {
			var _data = vm.$data;
			for (var key in _data) {
				(function(key) {
					Object.defineProperty(vm, key, {
						get: function() {
							return _data[key]
						},
						set: function(newValue) {
							_data[key] = newValue
							updata(vm, key, showPool)
						}
					})
				})(key);
			}
		}

	function initPool(container, methods, showPool, eventPool) {
		var allNodes = container.getElementsByTagName('*'),
			dom = null;
		for (var i = 0; i < allNodes.length; i++) {
			dom = allNodes[i]
			var vIfData = dom.getAttribute('v-if'),
				vShowData = dom.getAttribute('v-show'),
				vEvent = dom.getAttribute('@click');
			if (vIfData) {
				showPool.set(dom, { type: 'if', prop: vIfData })
				dom.removeAttribute('v-if')
			} else if (vShowData) {
				showPool.set(dom, { type: 'show', prop: vShowData })
				dom.removeAttribute('v-show')
			}
			if (vEvent) {
				eventPool.set(dom, methods[vEvent])
				dom.removeAttribute('@click')
			}
		}
	}

	function bindEvent(vm, eventPool) {
		for (let [dom, handler] of eventPool) {
			vm[handler.name] = handler
			dom.addEventListener('click', vm[handler.name].bind(vm), false)
		}
	}

	function render(vm, showPool, container,recyles) {
		recyles.beforeMount()
		var _data = vm.$data,
			_el = vm.$el,
			type = undefined;
		for (let [dom, info] of showPool) {
			type = info.type;
			if (type === 'if') {
				info.comment = document.createComment(['v-if']);
				!_data[info.prop] && (dom.parentNode.replaceChild(info.comment, dom))

			} else if (type === 'show') {
				!_data[info.prop] && (dom.style.display = 'none')
			}
		}
		_el.appendChild(container)
		recyles.mounted()
	}

	function updata(vm, key, showPool) {
		var _data = vm.$data,
			type = undefined;
		for (let [dom, info] of showPool) {
			if (info.prop === key) {
				type = info.type;
				if (type === 'if') {
					_data[info.prop] ?
						info.comment.parentNode.replaceChild(dom, info.comment) :
						dom.parentNode.replaceChild(info.comment, dom)

				} else if (type === 'show') {
					_data[info.prop] ? dom.removeAttribute('style') : dom.style.display = 'none'
				}
			}
		}
	}

	return MyVue;
})();

var vm = new myVue(App);
console.log(vm);