vue作为一个MVVM的js前端框架,它有一个核心就是双向数据绑定
作为一个MVVM的框架,可以拆分为三步来看它内部的实现原理,就是M→V,V→M,M→V
首先来看第一步,

    <div id="app">
        <input type="text" v-model="text">
        {{text}}
    </div>
    var vm=new Vue({
        el:'app',
        data:{
            text:'hello world!'
        }
    })

要先保证input框中的value值绑定到实例中的data中相对应的值

			function node2Fragment(node, vm) {
				//这里是dom劫持,vue会新建一个文档片段来替换dom中本来的结点
				var flag = document.createDocumentFragment();
				//子节点
				var child;
				while(child = node.firstChild) {
					//开始编译每个结点
					compile(child, vm);
					//appendchild方法会自动删除node对象的child结点
					flag.appendChild(child)
				}
				return flag;
			}

			function compile(node, vm) {
				var reg = /\{\{(.*)\}\}/;
				//节点类型为元素,这里第一个节点是input,所以进入第一个判断条件
				if(node.nodeType === 1) {
					var attr = node.attributes;
					for(var i = 0; i < attr.length; i++) {
						//匹配v-model这个属性名称
						if(attr[i].nodeName == 'v-model') {
							var name = attr[i].nodeValue;
							//将data的值赋给gainode
							node.value = vm.data[name];
						}
					}
				};
				//节点类型为text,这里为{{}},使用正则进入替换,替换为相对应的value
				if(node.nodeType === 3) {
					if(reg.test(node.nodeValue)) {
						var name = RegExp.$1; //指的是符合正则匹配的第一个
						name = name.trim();
						//将data的值赋给该node
						node.nodeValue = vm.data[name];
					}
				}
			}

先对节点进行劫持,检测到"v-model"属性所绑定的实例对象的data中所绑定的值,还有{{}}中绑定的值,对绑定的数据进行初始化,达到的效果为
这里写图片描述

第二步:
这一步是要达到用户改变input的值得时候,到同时改变绑定的Vue实例对象的data中的值

			function defineReactive(obj, key, val) {
				//定义一个主题
				var dep = new Dep();
				Object.defineProperty(obj, key, {
					get: function() {
						//添加订阅者watcher到主题对象Dep
						if(Dep.target) dep.addSub(Dep.target)
						return val
					},
					set: function(newVal) { //在set函数中检测到数据的值的变化
						if(newVal === val) return;
						val = newVal;
						//看到数据改变
						console.log("设置新的属性为" + val)
					}
				})
			}

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

在Vue的官网文档中有介绍数据的响应的中,有提到,在Vue实例进行初始化的时候,将一个object赋值给Vue实例的data的时候,Vue内部会对data中的每个值遍历执行转换为getter/setter(使用Object.defineProperty()),在每个值的set函数中可以执行数据发生变化的操作,因为这个转换时Vue实例初始化的时候就已经完成的,所以一切绑定需要事先在实例的data中定义好,之后不能再次添加,但是Vue提供了this.$set(),属性可以对本来初始化好的数据的对象上面添加新的属性和值

第三步:
这一步最后因为Vue实例中的data对应的值修改了,要反应到{{}}绑定的文本中,需要使用观察者模式(n对个观察者同时观察一个主题对象,当这个主题对象发生改变的时候,通知到每个观察者,做出相应的操作)

			function Watcher(vm, node, name) {
				//Dep.target是一个Dep的静态属性,表示当前观察者。
				Dep.target = this;
				this.name = name;
				this.node = node;
				this.vm = vm;
				//订阅者执行一次更新视图
				this.update();
				Dep.target = null;
			}
			Watcher.prototype = {
				update: function() {
					//触发对应data属性值的get函数
					this.get();
					this.node.nodeValue = this.value;
				},
				get: function() {
					this.value = this.vm[this.name]
				}
			}

			function Dep() {
				//主题的订阅者们
				this.subs = [];
			}
			Dep.prototype = {
				//添加订阅者的方法
				addSub: function(sub) {
					this.subs.push(sub);
				},
				//发布信息的方法(让订阅者们全部更新view)
				notify: function() {
					this.subs.forEach(function(sub) {
						sub.update();
					})
				}
			}

最后,使用

			//Vue对象
			function Vue(options) {
				this.data = options.data;
				var id = options.el;
				var data = this.data;

				//将data的属性全部通过访问器属性赋给vm对象,使读写vm实例的属性转成读写了vm.data的属性值,达到鱼目混珠的效果
				observe(data, this);

				var dom = node2Fragment(document.getElementById(id), this);

				//编译完成后,将dom片段添加到el挂载的元素上(app)
				document.getElementById(id).appendChild(dom)
			}

			//调用Vue
			var vm = new Vue({
				el: 'app',
				data: {
					texts: 'hello1',
				}
			})

就可以基本达到一个Vue使用数据双向绑定的一个目的
这里写图片描述

希望可以帮助你理解Vue中的数据相应
本文参考coderzzp,GitHub地址https://github.com/coderzzp/vue-come-true/tree/master/vue-come-true-First#%E6%9C%AC%E6%96%87%E7%9A%84%E5%AE%9E%E7%8E%B0%E7%9B%AE%E6%A0%87

Logo

前往低代码交流专区

更多推荐