vue 双向数据绑定的实现学习之监听器的实现方法
程序员文章站
2022-06-03 12:53:56
提到了vue实现的基本实现原理:object.defineproperty() -数据劫持 和 发布订阅者模式(观察者),下面讲的就是数据劫持在代码中的具体实现。
1.先...
提到了vue实现的基本实现原理:object.defineproperty() -数据劫持 和 发布订阅者模式(观察者),下面讲的就是数据劫持在代码中的具体实现。
1.先看如何调用
new一个对象,传入我们的参数,这个myvue ,做了啥?
上面看到了在实例化一个myvue 对象的时候,会执行init方法, init 方法做了两个事,调用了observer 方法,和 实例化调用了 compile 方法。 到这里我们就明白了,实例化一个myvue后,我们要做的就是监听数据变化和编译模板 。
上面object.key() 方法,实例化时传入的data里面对应的变量缓存到 myvue 对象的 $prop上,这样方便在后续处理数据。怎么个方便法呢!...
2.observer 的实现
observer ,模式里面的角色定位 他是一个发布者,也可以理解为是一个观察者
function observer (data) { if(!data || typeof data !== 'object') { return; } object.keys(data).foreach(key => { // 对每个属性监听处理 definereactive(data, key, data[key]); }) }
definereactive
function definereactive (data,key,value) { // 每次访问/修改属性的时候 实例化一个调度中心dep var dep = new dep(); object.defineproperty(data,key,{ get: function() { // 添加到watcher 的dep 调度中心 if (dep.target) { // dep.target 是个什么鬼? 转到watcher.js 它是某个订阅者 watcher dep.addsub(dep.target); //这个代码段的意思: 如果有订阅者(访问/修改属性的时候) 就将这个订阅者统一放进 dep 调度中心中 } // console.log(`${key}属性被访问了`) return value }, set: function (newvalue) { if (value != newvalue) { // console.log(`${key}属性被重置了`) value = newvalue dep.notify(); //我这里有做改动了,通知调度中心的notify方法 } } }) // 递归调用,observe 这个value observer(value) }
dep: 这里是所有订阅者的一个调度中心,它不是直接监听 发布者的信息,发布者将要发布的信息 发布到 一个中介、调度中心(dep),由这个dep 来调度信息给哪个订阅者(watcher)
// 统一管理watcher订阅者的dep (调度中心) dispatch center function dep () { // 所有的watcher 放进这里统一管理 this.subs = [] } dep.target = null; // 通知视图更新dom的 notify的方法 dep.prototype.notify = function () { // this.subs 是上面订阅器watcher 的集合 this.subs.foreach(sub => { // sub 是某个watcher 具体调用某个watcher的update 方法 sub.update() }) } // 添加订阅者的方法 dep.prototype.addsub = function (sub) { this.subs.push(sub) }
3.订阅器watcher
// 具体的订阅器watcher // 传入一个vue 的实例, 监听的属性, 以及处理的回调函数 function watcher (vm,prop,callback) { this.vm = vm; this.$prop = prop; this.value = this.get(); this.callback = callback; // 具体watcher所具有的方法,不同的watcher 不同的回调函数,处理不同的业务逻辑 } // 添加watcher 获得属性的get 方法,当有属性访问/设置 的时候,就产生订阅者 将这个订阅者放进调度中心 watcher.prototype.get = function () { dep.target = this; // 获得属性值 const value = this.vm.$data[this.$prop]; return value } // 添加watcher的更新视图的方法 watcher.prototype.update = function () { // 当属性值有变化的时候,执行方法,更新试图 const value = this.vm.$data[this.$prop]; const oldvalue = this.value; // update 执行的时候,先获取 vm 中data实时更新的属性值,this.value 是vm data中之前的老值 if (oldvalue != value) { // console.log('人家通知了,我要改变了') // 把刚刚获取的更新值赋给之前vm data 中的值 this.value = value // 执行回调函数 具体怎么处理这个,看实际调用时候 callback 的处理 this.callback(this.value) } }
4.模板编译
(为了直接看到页面数据变化的效果,在模板编译的核心数据处理上做了dom 操作,下一篇将讲模板编译的一些细节处理)
// dom模板编译 vm 就是我们最上面的myvue 对象 function compile (vm) { this.vm = vm; this.$el = vm.el; // this.data = vm.data; this.fragment = null; // 用作后面模板引擎 创建文档片段 this.init() } compile.prototype = { // init 方法简单处理,直接做dom 操作,后面会用详细的模板引擎的学习 init: function () { let value = this.vm.$data.name // 初始化获取到的值 放进dom节点中 document.queryselector('.form-control').value = value; document.queryselector('.template').textcontent = value // 通知订阅者更新dom new watcher(this.vm,this.vm.$prop, (value) => { document.queryselector('.form-control').value = value; document.queryselector('.template').textcontent = value }) document.queryselector('.form-control').addeventlistener('input',(e) => { let targetvalue = e.target.value if(value !== targetvalue) { this.vm.$data.name = e.target.value // 将修改的值 更新到 vm的data中 document.queryselector('.form-control').value = targetvalue; // 更新dom 节点 document.queryselector('.template').textcontent = targetvalue } },false) } }
这样就可以看到 在表单中,数据的双向绑定了。
未完待续,错误之处,敬请指出,共同进步!
下一篇 vue 双向数据绑定的实现学习(三)- 模板编译
附:演示代码:
js:
function myvue (options) { this.$options = options this.$el = document.queryselector(options.el); this.$data = options.data; object.keys(this.$data).foreach(key => { this.$prop = key; }) this.init() } myvue.prototype.init = function () { // 监听数据变化 observer(this.$data); // 获得值 // let value = this.$data[this.$prop]; // 不经过模板编译直接 通知订阅者更新dom // new watcher(this,this.$prop,value => { // console.log(`watcher ${this.$prop}的改动,要有动静了`) // this.$el.textcontent = value // }) //通知模板编译来执行页面上模板变量替换 new compile(this) } function observer (data) { if(!data || typeof data !== 'object') { return; } object.keys(data).foreach(key => { // 对每个属性监听处理 definereactive(data, key, data[key]); }) } function definereactive (data,key,value) { // 每次访问/修改属性的时候 实例化一个调度中心dep var dep = new dep(); object.defineproperty(data,key,{ get: function() { // 添加到watcher 的dep 调度中心 if (dep.target) { // dep.target 是个什么鬼? 转到watcher.js 它是某个订阅者 watcher dep.addsub(dep.target); //这个代码段的意思: 如果有订阅者(访问/修改属性的时候) 就将这个订阅者统一放进 dep 调度中心中 } // console.log(`${key}属性被访问了`) return value }, set: function (newvalue) { if (value != newvalue) { // console.log(`${key}属性被重置了`) value = newvalue dep.notify(); //我这里有做改动了,通知调度中心的notify方法 } } }) // 递归调用,observe 这个value observer(value) } // 统一管理watcher订阅者的dep (调度中心) dispatch center function dep () { // 所有的watcher 放进这里统一管理 this.subs = [] } dep.target = null; // 通知视图更新dom的 notify的方法 dep.prototype.notify = function () { // this.subs 是上面订阅器watcher 的集合 this.subs.foreach(sub => { // sub 是某个watcher 具体调用某个watcher的update 方法 sub.update() }) } // 添加订阅者的方法 dep.prototype.addsub = function (sub) { this.subs.push(sub) } // 具体的订阅器watcher // 传入一个vue 的示例, 监听的属性, 以及处理的回调函数 function watcher (vm,prop,callback) { this.vm = vm; this.$prop = prop; this.value = this.get(); this.callback = callback; // 具体watcher所具有的方法,不同的watcher 不同的回调函数,处理不同的业务逻辑 } // 添加watcher 获得属性的get 方法,当有属性访问/设置 的时候,就产生订阅者 将这个订阅者放进调度中心 watcher.prototype.get = function () { dep.target = this; // 获得属性值 const value = this.vm.$data[this.$prop]; return value } // 添加watcher的更新视图的方法 watcher.prototype.update = function () { // 当属性值有变化的时候,执行方法,更新试图 const value = this.vm.$data[this.$prop]; const oldvalue = this.value; // update 执行的时候,先获取 vm 中data实时更新的属性值,this.value 是vm data中之前的老值 if (oldvalue != value) { // console.log('人家通知了,我要改变了') // 把刚刚获取的更新值赋给之前vm data 中的值 this.value = value // 执行回调函数 具体怎么处理这个,看实际调用时候 callback 的处理 this.callback(this.value) } } // dom模板编译 vm 就是我们最上面的myvue 对象 function compile (vm) { this.vm = vm; this.$el = vm.el; // this.data = vm.data; this.fragment = null; // 用作后面模板引擎 创建文档片段 this.init() } compile.prototype = { // init 方法简单处理,直接做dom 操作,后面会用详细的模板引擎的学习 init: function () { let value = this.vm.$data.name // 初始化获取到的值 放进dom节点中 document.queryselector('.form-control').value = value; document.queryselector('.template').textcontent = value // 通知订阅者更新dom new watcher(this.vm,this.vm.$prop, (value) => { document.queryselector('.form-control').value = value; document.queryselector('.template').textcontent = value }) document.queryselector('.form-control').addeventlistener('input',(e) => { let targetvalue = e.target.value if(value !== targetvalue) { this.vm.$data.name = e.target.value // 将修改的值 更新到 vm的data中 document.queryselector('.form-control').value = targetvalue; // 更新dom 节点 document.queryselector('.template').textcontent = targetvalue } },false) } }
html:
<!doctype html> <html lang="en"> <head> <meta charset="utf-8"> <title>vue双向绑定原理及实现</title> <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@3.3.7/dist/css/bootstrap.min.css" integrity="sha384-bvyiisifek1dgmjrakycuhahrg32omucww7on3rydg4va+pmstsz/k68vbdejh4u" crossorigin="anonymous"> <style> #app { margin: 20px auto; width: 400px; padding: 50px; text-align: center; border: 2px solid #ddd; } </style> </head> <body> <div id="app"> <input class="form-control" v-model="name" type="text"> <h1 class="template">{{name}}</h1> </div> <script src="./js/index1.js"></script> <script> const vm = new myvue({ el: "#app", data: { name: "vue 双向数据绑定test1" } }); </script> </body> </html>
总结
以上所述是小编给大家介绍的vue 双向数据绑定的实现学习之监听器的实现方法,希望对大家有所帮助