20200308-学习MVVM思想

1.在学习Vue的过程中。发现后天的webpack都是配置,想提高开发效率,所以中断了Vue的学习进程(有兴趣的时候再去看吧)
2.这两天也是隔离的最后两天。于是3分热度的又想探索Vue是怎么实现双向绑定的。所以花了点时间学习了Vue MVVM的实现简单实现过程。https://www.bilibili.com/video/av54530725?t=2678&p=2
3.发现这个老师挺牛逼。手动实现,思路也挺清晰。
4.个人理解能力不强,看到第二个课程的时候跟不上了。没怎么明白是怎么实现双向绑定的。于是傻乎乎的跟着敲完实现了同样的功能。
5.我还是想弄清楚是一个怎么样的调用过程。于是乎花了几个小时的时间进行剖析代码执行
在这里插入图片描述

总的来说,
1.修改data中的对象声明方式。使用definepropery这个函数进行重新定义。使用这个函数的好处是,当数据发生改变的时候会触发get,set方法

	Object.defineProperty(obj,key,{
 			get(){
 				//创建watcher时,会取到对应的target内容
 				//取的时候 存放这个watcher
 				Dep.target && dep.addSub(Dep.target)
 				return value;
 			},set:
 				(newVal)=>{ //{{persson.name}} = {}
 				if(value != newVal){
 					this.observer(newVal) //所以要监控/防止 person ={a:1}被后台改了
 					value = newVal;
 					//设置的时候通知
 					dep.notify()
 				}
 			}
 		})

2.进行编译(让页面中使用Vue标签的展现为正常内容)

  • 添加watcher
  • 添加事件(input)
  • 替换html页面中展现的Vue标签-触发当前对象的set方法(Dep notify当前对象的观察者(watcher))

其他实现的一些内容还得重新理解理解。才能够自己动手的写出来

源码
Compiler.js

 class Compiler{
	constructor(el,vm){
		//元素 和Vue实例
		
		//el可能是字符串或者是document.getElementById("#app")
		//获取当前  元素---第一层咯,孩子和孙子还没加载到内存里面
		this.el = this.isElementNode(el)?el:document.querySelector(el);
		this.vm = vm;  //Vue实例
		//console.log(this.el)
		//将当前的模板替换  v-xxx  {{}}
		//将这些元素放在"内存"里面 文档碎片//替换完毕后再返回来
		let fragment = this.node2fragment(this.el);
		//console.log(fragment)
		//将节点中的元素进行替换
		
		//编译模板  attention
		this.compile(fragment)//用vm去编译 文档碎片
		
		///
		//再把节点赛道页面中---元素添加回去
		this.el.appendChild(fragment)
		
		
		
	}
	//编译文档碎片
	compile(node){
		//v-xxx  和 {{}}
		let childNodes = node.childNodes;//第一层节点,不包含孙子节点  (空文本)
		//console.log(childNodes)
		//类数组---》数组
		//let childNodesArray = this.classArray2Array(childNodes)
		;[...childNodes].forEach(child=>{
		//childNodesArray.forEach(child=>{
			//不是元素就是  空文本
			if(this.isElementNode(child)){
				//元素是否有v-???
				//console.log('element')
				this.compilerElement(child)
				this.compile(child)//子元素可能还有子元素
			}else{
				//文本则看是否有{{}}
				//console.log('text')
				this.compilerText(child)
			}
		}) 
	}
	/
	//判断是否v- 开头
	isDirective(attrName){
		return attrName.startsWith("v-")//v-开头
	}
	//万有引力
	//编译元素  是否有v-???
	compilerElement(node){
		//可能还有子元素
		let arrtributes = node.attributes
		;[...arrtributes].forEach(attr=>{
			//name = value
			//type="text"
			//v-model="person.name"
			let {name,value} = attr
			//判断名字是否是v-model  是不是v-开头的			 
			if(this.isDirective(name)){
				
				//console.log(name);//v-model
				let [,directive] = name.split("-")//model  调用方法model
				let [directiveName,eventName] = directive.split(":")//on   click
				
				//执行方法,去替换node中的内容,需要的内容从vm里面获得
				//                        node person.name    vm实例      事件名字click
				//       当前页面节点。绑定的表达式   vm实例(可以获得表达式的值)  事件名
				CompileUtil[directiveName](node,value,      this.vm,         eventName)//调用不同的指令来处理
			}
		})
		
	}
	//编译文本 是否有{{}}
	compilerText(node){
		let content = node.textContent;
		//匹配{{xxxx}}
		if(/\{\{(.+)\}\}/.test(content)){
			//console.log(content,'text')
			CompileUtil['text'](node,content,this.vm)
		}
	}
	/
	
	
	
	//由于语法不支持,自己写一个函数,类数组---》数组
	//已经可以了,类数组前面加上;[...Array]
	classArray2Array(classArray){
		let array = []
		for(let i=0,len=classArray.length;i<len;i++){
			array.push(classArray[i])
		}
		return array
	}
	
	
	//将node节点放在内存里面
	node2fragment(node){
		let fragment = document.createDocumentFragment();
		let firstChild
		while(firstChild = node.firstChild){
			//appendChild每拿到一个结点就从原来的位置删除
			fragment.appendChild(firstChild)//
		}
		return fragment;
	
	}
	
	
	//是不是元素
	isElementNode(node){
		return node.nodeType === 1;
	}
}

CompileUtil.js


CompileUtil = {
	getValue(vm,expr){//vm.$data  'person.name'.
		  
		return expr.split('.').reduce((data,current)=>{
			return data[current]//成为下一个的第一个参数
		},vm.$data)//初始化值
	},
	setValue(vm,expr,value){//person.name 
		//通过输入事件调用这个方法
	    expr.split('.').reduce((data,current,index,arr)=>{
	    	debugger
			if(index == arr.length-1){//最后一次
				return data[current] = value//调用set
			}
			return data[current]//调用get
		},vm.$data)//初始化值
	},
	model(node,expr,vm){
		//页面节点  绑定的表达式 person.name   vm实例---》可以通过vm实例获得表达式的值
		
		//node是节点,expr是表达式,vm对象
		//给输入框赋予value值 nade.value = xxx
		//modelUpdate(node,value){node.value = value}
		let fn = this.updater['modelUpdate']
		//
		//给输入框加上一个观察者,等会数据更新了 就触发方法
		//会拿新值 给输入框服务值
		//第二课加  数据一更新 就调用回调方法。。在观察者里面定义的。
		//数据一更新,视图调用这个方法
		//添加监控。
		//node 闭包,
		new Watcher(vm,expr,(newValue)=>{
			fn(node,newValue)//进行更新	//node.value = newValue
		}) 
		node.addEventListener('input',(e)=>{
			let value = e.target.value//获取当前输入的值
			//给当前的表达式设置新的值
			this.setValue(vm,expr,value);//ode.value = value
		})
		//
		
		let value = this.getValue(vm,expr)
		fn(node,value)//进行更新
		
	},
	html(node,expr,vm){//v-html="message"
		let fn = this.updater['htmlUpdater'] //修改内容
		new Watcher(vm,expr,(newValue)=>{
			fn(node,newValue)//进行更新	
		})  
		// 
		let value = this.getValue(vm,expr);
		fn(node,value)//进行更新
	},
	on(node,expr,vm,eventName){//v-on:click = "change" //点击的时候,执行自己定义的change方法
		  //change
		debugger
		//                    click    
		node.addEventListener(eventName,(e)=>{
			vm[expr].call(vm,e)///this.change这个方法是没有的
			                   //vm实例. 
			                   //vm[person.name]
		})
	},
	getContentValue(vm,expr){
		//遍历表达式 将内容 重新替换一个完整的内容
		//  +?  重复1次以上,说明前面的 . 
		//
		return expr.replace(/\{\{(.+?)\}\}/g,(...args)=>{
			return this.getValue(vm,args[1])//重新的把a的,b的值取一遍。
		})
	}
	,text(node,expr,vm){  //expr == {{a}}   {{b}}
		let fn = this.updater['textUpdater'];
		//.+?  当前 n个  又尽量少取
		let content = expr.replace(/\{\{(.+?)\}\}/g,(...args)=>{
			expr == {{a}}   {{b}}
			//不管b 变了,还是a变了,都
			//也就是添加监控,给a,b添加监控(观察者)
			new Watcher(vm,args[1],(newValue)=>{
				//不能直接用newValue 替换旧值
				//因为 可能是a的新值,可能是b的新值
				//取完以后,作为总共的值node赋值  全的哦
				
				fn(node,this.getContentValue(vm,expr))//返回一个全的 然后进行调用fn更新
			})
			return this.getValue(vm,args[1])
		})
		fn(node,content)
	},updater:{ 
		htmlUpdater(node,value){//xss攻击
			node.innerHTML = value;
		},
		modelUpdate(node,value){
			node.value = value
		},
		//处理文本节点
		textUpdater(node,value){
			node.textContent = value
		}
	}
}

Dep.js


 //订阅:存放观察者
 class Dep{
 	//如果数据一变动,就通知观察者进行依次更新
 	constructor(){
 		this.subs = []//存放所有的watcher
 		
 	}
 	//订阅  添加观察者
 	addSub(watcher){
 		this.subs.push(watcher)
 	}
 	//如果一会数据变动了。就调用 Watcher的某个方法
 	//发布
 	notify(){
 		//每个观察者进行修改
 		//关联订阅和观察者===========================================》
 		this.subs.forEach(watcher=>watcher.update())
 		
 	}
 }
 

MVVM.js

 
class Vue{
	//构造器构造Vue实例
	constructor(options){
		/*//console.log(options)*/
		this.$el = options.el;
		this.$data = options.data;
		let computed = options.computed
		let methods = options.methods
		//判断绑定的元素是否存在----编译模板
		if(this.$el){
			//绑定的元素,和当前实例Vue 传递过去,因为 绑定的元素需要 vue实例去操作
			
			///2  实现数据劫持
			//把数据 全部转换成用Object.defineProperty定义
			new Observer(this.$data)
			//
			
			
			
			//计算属性
			//有依赖关系,
			//{{newName}}  reduce.
			//vm.$data.getNeName
			debugger;
			for(let key in computed){
				Object.defineProperty(this.$data,key,{
					get:()=>{
						return computed[key].call(this)
					}
				})
			}
			
			//一点击按钮,就会去执行哪个方法
			//vm[change].call(vm,e)
			//vm[chage]===========>methods[change]
			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){ //this 里面有 person
			Object.defineProperty(this,key,{
				
				get(){
					//debugger;
					return data[key];//this.$data.person ==== this.person
				},
				//设置代理方法
				set(newVal){
					data[key] = newVal
				}
			})
		}
	}
}

Observer.js

//
 class Observer{ //实现数据劫持
 	constructor(data){
 		this.observer(data)
 	}
 	observer(data){
 		if(data && typeof data == 'object'){
 			//如果是对象 才进行观察
 			for(let key  in data){
 				//对象 key 值
 				this.defineReactive(data,key,data[key])
 			}
 		}
 	}
 	defineReactive(obj,key,value){
 		this.observer(value)//递归调用。。。。。。。。//person:[watcher,watcher] b:[watcher]
 		//改了person 就调用watcher
 		//给obj的key方法进行 重新设置
 		let dep = new Dep()//给没一个ie属性  都加上一个具有发布定语的功能1
 		
 		//Object.defineProperty(对象,对象,对象)
 		Object.defineProperty(obj,key,{ 
 			get(){
 				//创建watcher时,会取到对应的target内容
 				//取的时候 存放这个watcher
 				Dep.target && dep.addSub(Dep.target)
 				return value;
 			},set:	 			
 				(newVal)=>{ //{{persson.name}} = {}
	 				debugger;
	 				if(value != newVal){
	 					this.observer(newVal) //所以要监控/防止 person ={a:1}被后台改了
	 					value = newVal;
	 					//设置的时候通知
	 					dep.notify()
 				}
 			}
 		})
 	}
 }

Watcher.js


 //需要给页面上的 input  {{school.name}} 加上观察者
 //在哪里加呢?---》找到对应监控的文本哪里
 //输入框一输入,数据一变动,就得添加观察者。
 //CompileUtil中的model方法进行
 
 
 //实现。数据一变动就 进行编译。
 //模式:  观察者  发布订阅,被观察者
 //需要将被观察者 放到观察者里面去
 //第二课
 //文本处理的时候加观察者,元素处理的时候也有加观察者
 //一new Watcher 的时候,就会去  执行get方法
 //let value = CompileUtil.getValue(this.vm,this.expr);//
 class Watcher{
 	/*
 	 		vm.$watch(vm,'school.name',(newVal)=>{
 	 			console.log('school.name 的数据一变动就执行这个回调方法')
 	 		})
 	 * */
 	constructor(vm,expr,callBack){
 		this.vm = vm
 		this.expr = expr
 		this.callBack = callBack
 		//默认先存放老值 -==》有变动才 执行回调方法
 		this.oldValue = this.get()
 		
 	}
 	get(){
 		//vm.$data.person,vm.$data.person.name
 		//一获取值就会去调用Observer类的 get方法(已经被绑定了get set方法)
 		debugger;
 		Dep.target = this;// 先把自己放在this
 		
 		//取值,把这个观察者  和数据关联起来
 		let value = CompileUtil.getValue(this.vm,this.expr);//
 		Dep.target = null //不取消的话,任何值取值,都会添加watcher
 		return value;
 	}
 	update(){//数据变化后,会调用观察者的update方法
 		let newValue = CompileUtil.getValue(this.vm,this.expr);
 		if(newValue !== this.oldValue){
 			this.callBack(newValue);
 		}
 	} 
 }
 /
 
 

index.html

<!DOCTYPE html>
<html>
	<head>
		<meta charset="UTF-8">
		<title></title>
	</head>
	<body>
		<div id="app">
			动态绑定了person.Name<input type="text" v-model="person.name" />
			<br />
			显示person.name:{{person.name}}<br />
			显示person.age:{{person.age}}<br />
			<!--如果数据不变化,视图就不会刷新-->
			计算属性:{{NewName}} 
			<ul>
				<li>1</li>
				<li>2</li>
			</ul>
			<button v-on:click="change">修改name</button>
			<div v-html="message"></div>
		</div>
	</body>
	<!--<script type="text/javascript" src="../js/vue.js" ></script>-->
	<script type="text/javascript" src="js/Compiler.js"></script>
	<script type="text/javascript" src="js/CompileUtil.js"></script>
	<script type="text/javascript" src="js/Dep.js"></script>
	<script type="text/javascript" src="js/Observer.js"></script>
	<script type="text/javascript" src="js/Wather.js"></script>
	<script type="text/javascript" src="js/MVVM.js"></script>
	<script type="text/javascript">
		const app = new Vue({
			el:"#app",
			data:{
				person:{
					name:"wp",
					age:23
				},
				message:"<h1>welcom</h1>"
				
			},
			computed:{
				NewName:function(){
					return this.person.name + '架构'
				}
			},methods:{
				change:function(){
					debugger
					this.person.name = 'wp123'
				}
			}
		})
		
	</script>
		
</html>

文件分布
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值