vue数据双向绑定原理及代码实现方法
Admin 2021-04-06 群英技术资讯 1092 次浏览
这篇文章主要介绍vue数据双向绑定原理以及如何实现一个简易版的数据双向绑定,下面内容会比较详细的分析每一步的问题以及代码,所以对于vue源码初学者有一定的学习参考价值。对vue源码学习感兴趣的朋友继续往下看吧。
本文要实现的效果如下图所示:
本文用到的HTML和JS主体代码如下:
<div id="app"> <h1 v-text="msg"></h1> <input type="text" v-model="msg"> <div> <h1 v-text="msg2"></h1> <input type="text" v-model="msg2"> </div> </div>
let vm = new Vue({ el: "#app", data: { msg: "hello world", msg2: "hello xiaofei" } })
我们将按照下面三个步骤来实现:
首先,我们要创造一个Vue类,这个类接收一个 options 对象,同时,我们要对 options 对象中的有效信息进行保存;
然后,我们有三个主要模块:Observer、Compile、Wathcer,其中,Observer用来数据劫持的,Compile用来解析元素,Wathcer是观察者。可以写出如下代码:(Observer、Compile、Wathcer这三个概念,不用细究,后面会详解讲解)。
class Vue { // 接收传进来的对象 constructor(options) { // 保存有效信息 this.$el = document.querySelector(options.el); this.$data = options.data; // 容器: {属性1: [wathcer1, wathcer2...], 属性2: [...]},用来存放每个属性观察者 this.$watcher = {}; // 解析元素: 实现Compile this.compile(this.$el); // 要解析元素, 就得把元素传进去 // 劫持数据: 实现 Observer this.observe(this.$data); // 要劫持数据, 就得把数据传入 } compile() {} observe() {} }
在这一步,我们要实现页面的初始化,即解析出v-text和v-model指令,并将data中的数据渲染到页面中。
这一步的关键在于实现compile方法,那么该如何解析el元素呢?思路如下:
代码如下:(主要看compile那部分)
class Vue { // 接收传进来的对象 constructor(options) { // 获取有用信息 this.$el = document.querySelector(options.el); this.$data = options.data; // 容器: {属性1: [wathcer1, wathcer2...], 属性2: [...]} this.$watcher = {}; // 2. 解析元素: 实现Compile this.compile(this.$el); // 要解析元素, 就得把元素传进去 // 3. 劫持数据: 实现 Observer this.observe(this.$data); // 要劫持数据, 就得把数据传入 } compile(el) { // 解析元素下的每一个子节点, 所以要获取el.children // 备注: children 返回元素集合, childNodes返回节点集合 let nodes = el.children; // 解析每个子节点的指令 for (var i = 0, length = nodes.length; i < length; i++) { let node = nodes[i]; // 如果当前节点还有子元素, 递归解析该节点 if(node.children){ this.compile(node); } // 解析带有v-text指令的元素 if (node.hasAttribute("v-text")) { let attrVal = node.getAttribute("v-text"); node.textContent = this.$data[attrVal]; // 渲染页面 } // 解析带有v-model指令的元素 if (node.hasAttribute("v-model")) { let attrVal = node.getAttribute("v-model"); node.value = this.$data[attrVal]; } } } observe(data) {} }
这样,我们就实现页面的初始化了。
因为input带有v-model指令,因此我们要实现这样一个功能:在input框中输入字符,data中绑定的数据发生相应的改变。
我们可以在input这个元素上绑定一个input事件,事件的效果就是:将data中的相应数据修改为input中的值。
这一部分的实现代码比较简单,只要看标注那个地方就明白了,代码如下:
class Vue { constructor(options) { this.$el = document.querySelector(options.el); this.$data = options.data; this.$watcher = {}; this.compile(this.$el); this.observe(this.$data); } compile(el) { let nodes = el.children; for (var i = 0, length = nodes.length; i < length; i++) { let node = nodes[i]; if(node.children){ this.compile(node); } if (node.hasAttribute("v-text")) { let attrVal = node.getAttribute("v-text"); node.textContent = this.$data[attrVal]; } if (node.hasAttribute("v-model")) { let attrVal = node.getAttribute("v-model"); node.value = this.$data[attrVal]; // 看这里!!只多了三行代码!! node.addEventListener("input", (ev)=>{ this.$data[attrVal] = ev.target.value; // 可以试着在这里执行:console.log(this.$data), // 就可以看到每次在输入框输入文字的时候,data中的msg值也发生了变化 }) } } } observe(data) {} }
至此,我们已经实现了:当我们在input框中输入字符的时候,data中的数据会自动发生更新;
本小节的主要任务是:当data中的数据发生更新的时候,绑定了该数据的元素会在页面上自动更新视图。具体思路如下:
1) 我们将要实现一个 Wathcer 类,它有一个update方法,用来更新页面。观察者的代码如下:
class Watcher{ constructor(node, updatedAttr, vm, expression){ // 将传进来的值保存起来,这些数据都是渲染页面时要用到的数据 this.node = node; this.updatedAttr = updatedAttr; this.vm = vm; this.expression = expression; this.update(); } update(){ this.node[this.updatedAttr] = this.vm.$data[this.expression]; } }
2) 试想,我们该给哪些数据添加观察者?何时给数据添加观察者?
在解析元素的时候,当解析到v-text和v-model指令的时候,说明这个元素是需要和数据双向绑定的,因此我们在这时往容器中添加观察者。我们需用到这样一个数据结构:{属性1: [wathcer1, wathcer2...], 属性2: [...]},如果不是很清晰,可以看下图:
可以看到:vue实例中有一个$wathcer对象,$wathcer的每个属性对应每个需要绑定的数据,值是一个数组,用来存放观察了该数据的观察者。(备注:Vue源码中专门创造了Dep这么一个类,对应这里所说的数组,本文属于简易版本,就不过多介绍了)
3) 劫持数据:利用对象的访问器属性getter和setter做到当数据更新的时候,触发一个动作,这个动作的主要目的就是让所有观察了该数据的观察者执行update方法。
总结一下,在本小节我们需要做的工作:
完整代码如下:
class Vue { // 接收传进来的对象 constructor(options) { // 获取有用信息 this.$el = document.querySelector(options.el); this.$data = options.data; // 容器: {属性1: [wathcer1, wathcer2...], 属性2: [...]} this.$watcher = {}; // 解析元素: 实现Compile this.compile(this.$el); // 要解析元素, 就得把元素传进去 // 劫持数据: 实现 Observer this.observe(this.$data); // 要劫持数据, 就得把数据传入 } compile(el) { // 解析元素下的每一个子节点, 所以要获取el.children // 拓展: children 返回元素集合, childNodes返回节点集合 let nodes = el.children; // 解析每个子节点的指令 for (var i = 0, length = nodes.length; i < length; i++) { let node = nodes[i]; // 如果当前节点还有子元素, 递归解析该节点 if (node.children) { this.compile(node); } if (node.hasAttribute("v-text")) { let attrVal = node.getAttribute("v-text"); // node.textContent = this.$data[attrVal]; // Watcher在实例化时调用update, 替代了这行代码 /** * 试想Wathcer要更新节点数据的时候要用到哪些数据? * e.g. p.innerHTML = vm.$data[msg] * 所以要传入的参数依次是: 当前节点node, 需要更新的节点属性, vue实例, 绑定的数据属性 */ // 往容器中添加观察者: {msg1: [Watcher, Watcher...], msg2: [...]} if (!this.$watcher[attrVal]) { this.$watcher[attrVal] = []; } this.$watcher[attrVal].push(new Watcher(node, "innerHTML", this, attrVal)) } if (node.hasAttribute("v-model")) { let attrVal = node.getAttribute("v-model"); node.value = this.$data[attrVal]; node.addEventListener("input", (ev) => { this.$data[attrVal] = ev.target.value; }) if (!this.$watcher[attrVal]) { this.$watcher[attrVal] = []; } // 不同于上处用的innerHTML, 这里input用的是vaule属性 this.$watcher[attrVal].push(new Watcher(node, "value", this, attrVal)) } } } observe(data) { Object.keys(data).forEach((key) => { let val = data[key]; // 这个val将一直保存在内存中,每次访问data[key],都是在访问这个val Object.defineProperty(data, key, { get() { return val; // 这里不能直接返回data[key],不然会陷入无限死循环 }, set(newVal) { if (val !== newVal) { val = newVal;// 同理,这里不能直接对data[key]进行设置,会陷入死循环 this.$watcher[key].forEach((w) => { w.update(); }) } } }) }) } } class Watcher { constructor(node, updatedAttr, vm, expression) { // 将传进来的值保存起来 this.node = node; this.updatedAttr = updatedAttr; this.vm = vm; this.expression = expression; this.update(); } update() { this.node[this.updatedAttr] = this.vm.$data[this.expression]; } } let vm = new Vue({ el: "#app", data: { msg: "hello world", msg2: "hello xiaofei" } })
至此,代码就完成了。
以上就是关于vue数据双向绑定原理以及实现的介绍,希望对大家更好的理解和学习vue框架有所帮助,大家也能够尝试实现一个简易版的数据双向绑定
免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:mmqy2019@163.com进行举报,并提供相关证据,查实之后,将立刻删除涉嫌侵权内容。
猜你喜欢
匿名函数顾名思义指的是没有名字的函数,在实际开发中使用的频率非常高,也是学好JS的重点。下面本篇文章就来给大家详细介绍一下JavaScript中的匿名函数,希望对大家有所帮助!
本篇文章给大家带来了关于JavaScript的相关知识,其中主要整理了函数的定义与基本使用的相关问题,包括了用函数语句定义、函数的调用、未定义的实参等等内容,下面一起来看一下,希望对大家有帮助。
本文主要介绍了Vue实现组件间通信的几种方式,不同的场景使用不同的方式,基本满足所有开发场景中的通信需求,感兴趣的可以了解一下
目录ElementUI上传Base64编码后的图片安装ElementUI按需引入(当然如果需要你也可以全部引入)上传实现ElementUI把上传的图片转为Base64ElementUI上传Base64编码后的图片自我认为ElementUI还是一个很不错的写手机端的组件,所以这次做小项目的时候就用了ElementUI的U
这篇文章主要为大家详细介绍了JavaScript canvas实现刮刮乐案例,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
成为群英会员,开启智能安全云计算之旅
立即注册Copyright © QY Network Company Ltd. All Rights Reserved. 2003-2020 群英 版权所有
增值电信经营许可证 : B1.B2-20140078 粤ICP备09006778号 域名注册商资质 粤 D3.1-20240008