vue双向绑定实现原理

当我们学会使用一个东西的时候,就开始想要去知道这个东西是怎么实现的,这个也是我们一直继续探究下去的动力,博主学了vue的时间也比较长了,自己也写了一个demo,还在不定时更新,有兴趣的小伙伴可以去看看,如果这个项目能让您有所收获,那也是博主希望看到的,接下来也是聊聊自己开始学习vue一些实现原理的过程。

刚刚接触vue的时候,我们就发现了这样一个有趣的功能,当我们在input输入框输入内容时,旁边也会对应的显示同样内容
在这里插入图片描述

看起来似乎其实是个非常简单的功能,但是vue背后实现却比较复杂,博主也是查阅了很多资料,从中也是学习到了很多之前忽略的东西,vue的响应式原理首先就是利用了对象的访问器属性,就是setter/getter方法,并且利用es6的Object.defineProperty()的方法就能去设置这个方法。

先来看下html部分的代码

<div id="app">
	<input type="text" v-model="text">
	{{text}}
</div>

非常简单的代码,相信大家都能看懂,我们以上面的html页面为例,一步一步来解析vue响应式原理的实现步骤

function defineReactive (obj, key, val) {
			var dep = new Dep();
			//	2.给所有的data对象的属性附加上访问器get/set属性
			Object.defineProperty(obj, key, {
				get: function () {
					//	13.此时Dep.target就是指向了Watcher,所以会执行addSub的方法
					if (Dep.target) dep.addSub(Dep.target);
					return val;
				},
				set: function (newValue) {
					if (val === newValue) return;
					val = newValue;
					//	21.此时dep调用notify方法发布通知
					dep.notify();
				}
			})
		}

		function observe (obj, vm) {
			Object.keys(obj).forEach(function(key){
				defineReactive(vm, key, obj[key]);
			})
		}

		function nodeToFragment (node, vm) {
			var flag = document.createDocumentFragment();
			var child;
			while (child = node.firstChild) {
				//	4.取出节点上的data中对应的属性的值进行编译处理
				//	7.再次循环子节点,取到包含text变量的文本节点
				compile(child, vm);
				flag.append(child);
			}
			//	18.node子节点遍历完成,退出循环,返回dom片段
			return flag;
		}

		function compile (node, vm) {
			var reg = /\{\{(.*)\}\}/;
			//节点类型为元素
			if (node.nodeType === 1) {
				var attrs = node.attributes;
				for (var i = 0;i < attrs.length;i++) {
					if (attrs[i].name === 'v-model') {
						var name = attrs[i].nodeValue;	//	如果是属性,则其nodeValue返回就是属性的值
						//	5.找到input子节点,添加事件监听
						//	20.这个时候如果我们修改input里面的值,事件监听就会重新设置vm实例的值,触发访问器的set方法
						node.addEventListener('input',function(e){
							vm[name] = e.target.value;
						});
						//	6.把实例中data对象对应上面的name属性的值赋值给子节点的value值,触发访问器get
						//	注意此时的Dep.target还是undefined
						node.value = vm[name]
						node.removeAttribute('v-model')
					}
				}
			}

			//节点类型为文本
			if (node.nodeType === 3) {
				if (reg.test(node.nodeValue)) {
					var name = RegExp.$1;	//	获取匹配的正则表达式的第一个括号的表达式
					name = name.trim();
					//	8.符合文本的条件,创建观察者实例,进入观察者构造函数
					new Watcher(vm, node, name);
				}
			}
		}

		function Watcher (vm, node, name) {
			//	9.此时将Watcher实例赋给了Dep.target
			Dep.target = this;
			this.name = name;
			this.node = node;
			this.vm = vm;
			//	10.执行Watcher原型上的update方法
			this.update();
			//	17.清空Dep.target
			Dep.target = null;
		}

		Watcher.prototype = {
			//	23.从11步开始重复执行,将改变的vm中data的值赋给了文本节点
			update: function () {
				//	11.先执行原型上的get()方法
				this.get();
				//	16.将这个value为'hello vue'的值赋给了文本节点的nodeValue值
				this.node.nodeValue = this.value;
			},
			get: function () {
				//	12.get方法将vm中的name属性值赋给value属性,触发vm上data的访问器get
				//	15.返回了val的属性'hello vue'
				this.value = this.vm[this.name];
			}
		}

		function Dep () {
			this.subs = [];
		}

		Dep.prototype = {
			//	14.将Watcher放入subs的数组中
			addSub: function (sub) {
				this.subs.push(sub);
			},
			//	22.执行存储在subs数组中的Watcher观察者的update方法
			notify: function () {
				this.subs.forEach(function(sub){
					sub.update();
				})
			}
		}

		function Vue (options) {
			this.data = options.data;
			var data = this.data;
			//	1.遍历data对象上的所有属性
			observe(data, this);

			var id = options.el;
			//	3.找到app根节点下的子节点,创建dom片段
			var dom = nodeToFragment(document.getElementById(id), this);
			//	19.将dom片段挂载到这个根元素app下
			document.getElementById(id).append(dom);
		}

		var vm = new Vue({
			el: 'app',
			data: {
				text: 'hello vue'
			}
		})

博主也是将详细的步骤也是写在了注释上面,如果有的同学不理解的话,可以利用Chrome浏览器的断点调试功能来查看代码的执行步骤,此外这里也是应用到了一种叫做发布订阅的模式,博主也是采用了设计模式一书中的概括

一个或多个观察者对目标的状态感兴趣,它们通过将自己依附在目标对象上一边注册所感兴趣的内容,目标状态发生改变并且观察者可能对这些改变感兴趣,就会发送一个通知消息,调用每个观察者的更新方法。当观察者不再对目标状态感兴趣时,它们可以简单地将自己从中分离。

看到这个定义是不是对于上面的代码有了一些认识的呢,正是利用了这种模式来实现一个简单的vue响应式原理,就是这样简单的代码其中包含的东西也是非常多,需要有很多知识的铺垫才能理解,所以前路很长,还有很多等待我们去学习。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值