手动实现vue数据双向绑定

手动实现vue数据双向绑定

1.看了vue 数据双向绑定的源码决定 根据自己的理解 实现一个简单版的 vue双向绑定

  • 首先根据用法 new Vue({…}) 可以知道 vue 构造函数接收一个 对象参数


class  Vue {

	constructor(options){
			
	}

}
  • 其次 数据双向绑定的原理 是使用了观察者模式 和 Object.defineProperty 方法对vue实例data项的属性进行劫持,所以我们先实现这两步。代码如下

class Vue {

	constructor(options){
		this.$data = options.data || {}; //对data 属性进行初始化
		this.dep = new Dep();  // 初始化订阅列表
		this.Observer(this.$data); //将data 对象传入 观察者方法 内进行 属性劫持
	}
	//观察者
	Observer(data){
		
		for(let key in data){  // 对data 对象 进行 遍历
			let val = data[key]; // 将data 属性值保存出来 
			this.dep[key] = [];  // 将每个属性都注册一个 订阅列表  key 值作为 唯一标识
			 //此处 劫持 this 而不劫持this.$data 是为了 在vue 中可以直接使用 this.key 的方式 访问 this.$data 内的属性。相当与将this.$data 的属性代理到 this 上 
			Object.defineProperty(this,key,{ 
				get:function(){
					return val;//直接 返回 this.$data 内的对应属性值
				},
				set:function(newVal){
					if(newVal === val) return;
					val = newVal;
					//数据 更新  触发 相应的 订阅列表 进行 视图的更新
					this.dep.noify(key);   // 将key 值传入 触发 对应的 订阅列表内的 方法
				}
			})
		}
		
	}
}
// 订阅列表
class Dep{
	constructor(){

	}
	//触发 发布信息
	noify(key){
		this[key].forEach(item=>{
			item.update();   //执行 对应的 更新方法
		})
	}
}
  • 上述已经实现了基本的结构功能,还需要实现 一个监听器 来提供相应的 更新方法
class Vue {

	constructor(options){
		this.$data = options.data || {}; //对data 属性进行初始化
		this.dep = new Dep();  // 初始化订阅列表
		this.Observer(this.$data); //将data 对象传入 观察者方法 内进行 属性劫持
	}
	//观察者
	Observer(data){
		
		for(let key in data){  // 对data 对象 进行 遍历
			let val = data[key]; // 将data 属性值保存出来 
			this.dep[key] = [];  // 将每个属性都注册一个 订阅列表  key 值作为 唯一标识
			 //此处 劫持 this 而不劫持this.$data 是为了 在vue 中可以直接使用 this.key 的方式 访问 this.$data 内的属性。相当与将this.$data 的属性代理到 this 上 
			Object.defineProperty(this,key,{ 
				get:function(){
					return val;//直接 返回 this.$data 内的对应属性值
				},
				set:function(newVal){
					if(newVal === val) return;
					val = newVal;
					//数据 更新  触发 相应的 订阅列表 进行 视图的更新
					this.dep.noify(key);   // 将key 值传入 触发 对应的 订阅列表内的 方法
				}
			})
		}
	}
}
// 订阅列表
class Dep{
	constructor(){

	}
	//触发 发布信息
	noify(key){
		this[key].forEach(item=>{
			item.update();   //执行 对应的 更新方法
		})
	}
}

//为了看起来方便  将上述代码 一起 copy 下来并做稍微的更改
//监听器
class Watcher {
	constructor(vm,elem,cb){  // vm 就是需要更新的 vue 实例    elem 就是需要更新 dom节点   cb 就是提供的更新回调函数
		this.vm = vm;
		this.elem = elem;
		this.cb = cb;
	}
	update(){
		this.cb.call(this);  //此处使用call 方法来将this 指向 当前的 watcher 监听实例
	}
}
  • 监听器已经有了,还需要一个收集依赖的方法,vue中是在解析渲染DOM的时候进行这些依赖的收集,下面实现这个complie 方法 并 将上述的代码 进行 改进 实现最终版本。
class Vue {

	constructor(options){
		this.$data = options.data || {}; //对data 属性进行初始化
		this.$el = document.querySelector(options.el); // 此处只考虑传入 el 属性的vue实例 template 不考虑(过于麻烦) 根据传入的el 属性值选择出挂载节点
		this.dep = new Dep();  // 初始化订阅列表
		this.Observer(this.$data); //将data 对象传入 观察者方法 内进行 属性劫持
		this.Complie(this.$el); // 解析
	}
	//观察者
	Observer(data){
		
		for(let key in data){  // 对data 对象 进行 遍历
			let val = data[key]; // 将data 属性值保存出来 
			this.dep[key] = [];  // 将每个属性都注册一个 订阅列表  key 值作为 唯一标识
			 //此处 劫持 this 而不劫持this.$data 是为了 在vue 中可以直接使用 this.key 的方式 访问 this.$data 内的属性。相当与将this.$data 的属性代理到 this 上 
			Object.defineProperty(this,key,{ 
				get:function(){
					return val;//直接 返回 this.$data 内的对应属性值
				},
				set:function(newVal){
					if(newVal === val) return;
					val = newVal;
					//数据 更新  触发 相应的 订阅列表 进行 视图的更新
					this.dep.noify(key);   // 将key 值传入 触发 对应的 订阅列表内的 方法
				}
			})
		}
	}
	//解析器  (收集依赖)
	Complie(el){
		const children = el.children || []; // 防止undefined 报错
		const vm = this; // 将vue实例保存,用于后续的使用  主要是防止this指向性的问题出现
		for(let element of children){
			//此处实现双向数据绑定 就暂时实现 v-text   v-model 指令
			if(element.hasAttribute('v-model')){
				const exp = element.getAttribute('v-model'); // 获取到 依赖属性
				
				//进行input事件的处理
				element.oninput = function(e){
					vm[exp] = e.target.value; // 视图更新 触发 数据更新
				}
				//找到对应的订阅者列表 添加监听者实例(订阅者)
				vm.dep[exp].push(new Watcher(vm,element,function(){  //更新回调函数 函数内部对视图进行更新					//此处this 最终调用时 会通过call 指向 watcher 实例
					this.elem.value = this.vm[exp];
				}))
			}
			//同理 对 v-text 指令进行处理
			if(element.hasAttribute('v-text')){
				const exp = element.getAttribute('v-text');// 获取到 依赖属性

				vm.dep[exp].push(new Watcher(vm,element,function(){
					this.elem.innerText = this.vm[exp];//此处为什么 能访问 exp 变量   =》 闭包知识点 
				}))
			}
			//  如果当前元素还有子元素 那么进行递归 解析
			if(element.children && element.children.length){
				this.Complie(element);  
			}
		}
		
	}
}
// 订阅列表
class Dep{
	constructor(){

	}
	//触发 发布信息
	noify(key){
		this[key].forEach(item=>{
			item.update();   //执行 对应的 更新方法
		})
	}
}

//为了看起来方便  将上述代码 一起 copy 下来并做稍微的更改
//监听器
class Watcher {
	constructor(vm,elem,cb){  // vm 就是需要更新的 vue 实例    elem 就是需要更新 dom节点   cb 就是提供的更新回调函数
		this.vm = vm;
		this.elem = elem;
		this.cb = cb;
	}
	update(){
		this.cb.call(this);  //此处使用call 方法来将this 指向 当前的 watcher 监听实例
	}
}
  • 简易的vue实现完成,看一下效果
//html
<div id="app">
        <h3>this is vue 2.x</h3>
        <input type="text" v-model="inputValue" />
        <p v-text="inputValue"></p>
</div>


//js
new Vue({
	el:'#app',
	data:{
		inputValue:''
	}
})

//效果实现
在这里插入图片描述

发布了96 篇原创文章 · 获赞 64 · 访问量 3万+

猜你喜欢

转载自blog.csdn.net/qq_41709082/article/details/104553843
今日推荐