ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

MVVM实现类似Vue的基本功能

2021-07-10 20:04:03  阅读:150  来源: 互联网

标签:node Vue MVVM expr vm value 基本功能 let data


html文件中引入自定义的MVVM.js

<!DOCTYPE html>
<html>
	<head>
		<meta charset="utf-8">
		<title></title>
	</head>
	<body>
		<div id="app">
			<input type="text" v-model="school.name" />
			{{school.name}}
			<div>{{school.name}}</div>
			<div>{{school.age}}</div>
			<div v-html="message"></div>
			
			<ul>
				<li>1</li>
				<li>2</li>
			</ul>
			{{getMyLove}}
			<button v-on:click="change">+</button>
		</div>
		<script src="./自定义mvvm.js"></script>
		<script>
			let vm=new Vue({
				el:"#app",
				data:{
					school:{
						name: "宋杨",
						age:22
					},
					message:"<h1>大家好</h1>"
				},
				computed:{
					getMyLove(){
						return this.school.name+"喜欢琳琳";
					}
				},
				methods:{
					change(){
						this.school.age=this.school.age+1;
						//为什么这样写不行 this.school.age=this.school.age++;
					}
				}
			})
		</script>
	</body>
</html>

我们需要先定义几个关键的类:

Vue:整个框架的入口。
Observer:把$data数据全部转化成Object.defineProperty()来定义。数据劫持。
Watcher:观察者,每个使用到数据的地方都要创建观察者,并且绑定到对应的被观察者身上,该过程就是订 阅。
Dep:是一个包含发布和订阅的功能,data里面的每个数据都需要有一个Dep实例。
Compiler:用来编译模版的,简单实现了虚拟DOM的功能。

2.简述一下大致的流程。
首先,进入的是vue实例,在里面获取对应的el、data、computed、method等数据。先把data的数据全部转化成Object.defineProperty()定义。然后把computed、method这些方法添加代理,例如,用this.XXX就能指代this.computed.XXX()。再把数据获取操作vm.$data上的取值操作 都代理到 vm上。最后就可以创建Compiler实例编译模版了。
注意:代理操作其实是更方便新手入门,更简单的操作数据。
Observer类里面会将data对象里面的属性全部用Object.defineProperty()定义,若属性也是对象,就把该属性里面的子属性也重定义,使用了递归的思想。同时,在每一个的get方法里面都有订阅观察者的功能,在每一个set方法里面都有发布功能。
Dep就是在Observer类中使用订阅和发布功能时候需要调用的类。
Compiler模版编译中,先获取到对应的根节点,用document。createDocumentFragment()创建文档碎片,将根结点的child都用append移动到文档碎片中。这样的话就相当于是虚拟Dom,最后使用的时候把该文档碎片塞回到页面中。
同时分别对元素和文本编译,获取到对应的指令或{{}},对v-model和{{}}都要添加观察者,并且把对应的值传到页面中。

MVVM.js

class Compiler {
	constructor(el, vm) {
		// 判断el是字符串还是元素。如果是字符串,通过字符串获取相应元素
		this.el = this.isElementNode(el) ? el : document.querySelector(el);

		// 把当前节点中的元素 获取到 放到内存储中
		this.vm = vm;
		let fragment = this.createFragment(this.el);

		// 把节点中的内容替换
		// 用数据编译模版
		this.compiler(fragment);

		// 把内容塞到页面中

		this.el.appendChild(fragment);

	}
	isDirective(attrName) { //判断是不是指令
		return attrName.startsWith('v-');
	}
	compilerElement(node) { //编译元素
		let attributes = node.attributes;
		[...attributes].forEach(attr => {
			let {name,value:expr} = attr;
			if (this.isDirective(name)) {
				let [,directive]=name.split('-');
				
				let [directiveName,eventName]=directive.split(':');
				
				//需要调用不同的指令来处理
				CompilerUtil[directiveName](node,expr,this.vm,eventName);
			}
		})
	}
	compilerText(node) { //编译文本,判断文本中是否有{{}}
		let content = node.textContent;
		if(/\{\{(.+?)\}\}/.test(content)){
			//文本节点
			CompilerUtil['text'](node,content,this.vm);//{{a}} {{b}}
		}
	}
	//核心编译方法
	compiler(node) { //编译内存中dom
		let childNodes = node.childNodes;

		[...childNodes].forEach(child => {
			if (this.isElementNode(child)) {
				this.compilerElement(child);
				//如果是元素的话 需要把自己传进去 再去遍历子节点
				this.compiler(child);

			} else {
				this.compilerText(child);
			}
		})

	}

	createFragment(node) { //把节点移动到内存中
		// 创建文档碎片
		let fragment = document.createDocumentFragment();
		let firstChild;
		while (firstChild = node.firstChild) {
			// appendChild()具有移动性
			fragment.appendChild(firstChild);
		}
		return fragment;
	}
	isElementNode(node) { //判断是否为元素节点
		return node.nodeType === 1;
	}

}
CompilerUtil={
	getValue(vm,expr){
		
			return expr.split('.').reduce((data,current)=>{
				return data[current];
			},vm.$data)
		
	},
	setValue(vm,expr,value){
		expr.split('.').reduce((data,current,index,arr)=>{
			if(index==arr.length-1){
				return data[current]=value;
			}
			return data[current];
		},vm.$data)
	},
	on(node,expr,vm,eventName){
		node.addEventListener(eventName,(e)=>{
			vm[expr].call(vm,e);
		});
	},
	model(node,expr,vm){
		let fn=this.updater['modeUpdater'];
		
		new Watcher(vm,expr,(newValue)=>{//添加观察者
			fn(node,newValue);
		});
		node.addEventListener('input',(e)=>{
			let value=e.target.value;
			this.setValue(vm,expr,value);
		});
		let value=this.getValue(vm,expr);
		fn(node,value)
	},
	html(node,expr,vm){
		let fn=this.updater['htmlUpdater'];
		
		new Watcher(vm,expr,(newValue)=>{//添加观察者
			fn(node,newValue);
		});
		let value=this.getValue(vm,expr);
		fn(node,value)
	},
	getContentValue(vm,expr){
		return expr.replace(/\{\{(.+?)\}\}/g,(...args)=>{
			return this.getValue(vm,args[1]);
		});
	},
	text(node,expr,vm){
		let fn=this.updater['textUpdater'];
		let content=expr.replace(/\{\{(.+?)\}\}/g,(...args)=>{
			new Watcher(vm,args[1],()=>{//添加观察者
				fn(node,this.getContentValue(vm,expr));//返回一个全的字符串
			}); 
			return this.getValue(vm,args[1]);
			
		});
		fn(node,content);
		
	},
	updater:{
		modeUpdater(node,value){
			node.value=value;
		},
		htmlUpdater(node,value){
			node.innerHTML=value;
		},
		textUpdater(node,value){
			node.textContent=value;
		}
	}
}


//观察者 (发布订阅) 观察者(多) 被观察者(一)
class Dep {
	constructor() {
		this.subs=[];//存放所有的watcher
	}
	//订阅
	addSub(watcher){//添加watcher方法
		this.subs.push(watcher);
	}
	
	//发布
	notify(){
		this.subs.forEach(watcher=>watcher.updata());
	}
}

// vm.$watch(vm,'school.name',(newValue)=>{})
class Watcher{
	constructor(vm,expr,cb){
		this.vm=vm;
		this.expr=expr;
		this.cb=cb;
		this.oldValue=this.get();
	}
	get(){
		//取数据之前吧当前的观察者放到Dep.target里面
		Dep.target=this;
		//由于getValue()会触发Object.defineProperty的get方法,在get里面会将此watcher添加到所属的Dep实例里面。
		let value=CompilerUtil.getValue(this.vm,this.expr);
		//取完数据之后必须赋空,否则后面其他地方会无法更新Dep.target的值,意味着后面的订阅都会出错。
		Dep.target=null;
		return value;
	}
	updata(){
		let newValue =CompilerUtil.getValue(this.vm,this.expr);
		if(newValue != this.oldValue){
			this.cb(newValue);
		}
	}
}

class Observer{
	constructor(data) {
	    this.observer(data);
	}
	// 实现数据劫持功能
	observer(data){
		//如果是对象才观察
		if(data && typeof data == 'object'){
			//如果是对象
			for(let key in data){
				this.defineReactive(data,key,data[key]);
			}
		}
	}
	defineReactive(obj,key,value){
		this.observer(value);
		let dep =new Dep();//给每一个属性都加上具有发布和订阅的功能
		Object.defineProperty(obj,key,{
			get(){
				
				Dep.target && dep.addSub(Dep.target);
				return value;
			},
			set:(newValue)=>{
				if(value != newValue){
					// 赋的新值,需要把它再监控下
					this.observer(newValue);
					value=newValue;
					dep.notify();
				}
				
			}
		});
	}
}

class Vue {
	constructor(options) {
		this.$el = options.el;
		this.$data = options.data;
		let computed=options.computed;
		let methods=options.methods;
		
		//根结点存在,编译模版
		if (this.$el) {
			// 把数据全部转化成Object。defineProperty()来定义
			new Observer(this.$data);
			
			for(let key in computed){
				Object.defineProperty(this.$data,key,{
					get:()=>{
						return computed[key].call(this)
					}
				});
			}
			
			for(let key in methods){
				Object.defineProperty(this,key,{
					get(){
						return methods[key];
					}
				});
			}
			
			// 把数据获取操作 vm上的取值操作 都代理到vm.$data
			this.proxyVm(this.$data);
			
			new Compiler(this.$el, this);
		}
	}
	proxyVm(data){
		for(let key in data){
			Object.defineProperty(this,key,{
				get(){
					return data[key]; //进行转化
				},
				set(newValue){
					data[key]=newValue;
				}
			});
		}
	}

}


标签:node,Vue,MVVM,expr,vm,value,基本功能,let,data
来源: https://www.cnblogs.com/songcubi/p/14994650.html

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有