vue框架实现双向数据绑定的详细方法操作是什么
Admin 2022-06-11 群英技术资讯 312 次浏览
主要是通过数据劫持和发布订阅一起实现的
<div id="app"> <form> <input type="text" v-model="username"> </form> <p v-bind="username"></p> </div>
简单的模拟Vue类
将实例化时的选项options, 数据options.data进行保存 此外,通过options.el获取dom元素,存储到$el上
class MyVue { constructor(options) { this.$options = options this.$el = document.querySelector(this.$options.el) this.$data = options.data } }
实例化一个MyVue,传递选项进去,选项中指定绑定的元素el和数据对象data
const myVm = new MyVue({ el: '#app', data: { username: 'LastStarDust' } })
劫持数据是为了修改数据的时候可以感知, 发出通知, 执行更新视图操作
class MyVue { constructor(options) { // ... // 监视数据的属性 this.observable(this.$data) } // 递归遍历数据对象的所有属性, 进行数据属性的劫持 { username: 'LastStarDust' } observable(obj) { // obj为空或者不是对象, 不做任何操作 const isEmpty = !obj || typeof obj !== 'object' if(isEmpty) { return } // ['username'] const keys = Object.keys(obj) keys.forEach(key => { // 如果属性值是对象,递归调用 let val = obj[key] if(typeof val === 'object') { this.observable(val) } // this.defineReactive(this.$data, 'username', 'LastStarDust') this.defineReactive(obj, key, val) }) return obj } // 数据劫持,修改属性的get和set方法 defineReactive(obj, key, val) { Object.defineProperty(obj, key, { enumerable: true, configurable: true, get() { console.log(`取出${key}属性值: 值为${val}`) return val }, set(newVal) { // 没有发生变化, 不做更新 if(newVal === val) { return } console.log(`更新属性${key}的值为: ${newVal}`) val = newVal } }) } }
存储订阅者, 收到通知时,取出订阅者,调用订阅者的update方法
// 定义消息订阅器 class Dep { // 静态属性 Dep.target,这是一个全局唯一 的Watcher,因为在同一时间只能有一个全局的 Watcher static target = null constructor() { // 存储订阅者 this.subs = [] } // 添加订阅者 add(sub) { this.subs.push(sub) } // 通知 notify() { this.subs.forEach(sub => { // 调用订阅者的update方法 sub.update() }) } }
为每一个属性添加订阅者
defineReactive(obj, key, val) { const dep = new Dep() Object.defineProperty(obj, key, { enumerable: true, configurable: true, get() { // 会在初始化时, 触发属性get()方法,来到这里Dep.target有值,将其作为订阅者存储起来,在触发属性的set()方法时,调用notify方法 if(Dep.target) { dep.add(Dep.target) } console.log(`取出${key}属性值: 值为${val}`) return val }, set(newVal) { // 没有发生变化, 不做更新 if(newVal === val) { return } console.log(`更新属性${key}的值为: ${newVal}`) val = newVal dep.notify() } }) }
从模型中取出数据并更新视图
// 定义订阅者类 class Wather { constructor(vm, exp, cb) { this.vm = vm // vm实例 this.exp = exp // 指令对应的字符串值, 如v-model="username", exp相当于"username" this.cb = cb // 回到函数 更新视图时调用 this.value = this.get() // 将自己添加到消息订阅器Dep中 } get() { // 将当前订阅者作为全局唯一的Wather,添加到Dep.target上 Dep.target = this // 获取数据,触发属性的getter方法 const value = this.vm.$data[this.exp] // 在执行添加到消息订阅Dep后, 重置Dep.target Dep.target = null return value } // 执行更新 update() { this.run() } run() { // 从Model模型中取出属性值 const newVal = this.vm.$data[this.exp] const oldVal = this.value if(newVal === oldVal) { return false } // 执行回调函数, 将vm实例,新值,旧值传递过去 this.cb.call(this.vm, newVal, oldVal) } }
// 定义解析器 // 解析指令,替换模板数据,初始视图 // 模板的指令绑定更新函数, 数据更新时, 更新视图 class Compile { constructor(el, vm) { this.el = el this.vm = vm this.init(this.el) } init(el) { this.compileEle(el) } compileEle(ele) { const nodes = ele.children // 遍历节点进行解析 for(const node of nodes) { // 如果有子节点,递归调用 if(node.children && node.children.length !== 0) { this.compileEle(node) } // 指令时v-model并且是标签是输入标签 const hasVmodel = node.hasAttribute('v-model') const isInputTag = ['INPUT', 'TEXTAREA'].indexOf(node.tagName) !== -1 if(hasVmodel && isInputTag) { const exp = node.getAttribute('v-model') const val = this.vm.$data[exp] const attr = 'value' // 初次模型值推到视图层,初始化视图 this.modelToView(node, val, attr) // 实例化一个订阅者, 将更新函数绑定到订阅者上, 未来数据更新,可以更新视图 new Wather(this.vm, exp, (newVal)=> { this.modelToView(node, newVal, attr) }) // 监听视图的改变 node.addEventListener('input', (e) => { this.viewToModel(exp, e.target.value) }) } // 指令时v-bind if(node.hasAttribute('v-bind')) { const exp = node.getAttribute('v-bind') const val = this.vm.$data[exp] const attr = 'innerHTML' // 初次模型值推到视图层,初始化视图 this.modelToView(node, val, attr) // 实例化一个订阅者, 将更新函数绑定到订阅者上, 未来数据更新,可以更新视图 new Wather(this.vm, exp, (newVal)=> { this.modelToView(node, newVal, attr) }) } } } // 将模型值更新到视图 modelToView(node, val, attr) { node[attr] = val } // 将视图值更新到模型上 viewToModel(exp, val) { this.vm.$data[exp] = val } }
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Document</title> </head> <body> <div id="app"> <form> <input type="text" v-model="username"> </form> <div> <span v-bind="username"></span> </div> <p v-bind="username"></p> </div> <script> class MyVue { constructor(options) { this.$options = options this.$el = document.querySelector(this.$options.el) this.$data = options.data // 监视数据的属性 this.observable(this.$data) // 编译节点 new Compile(this.$el, this) } // 递归遍历数据对象的所有属性, 进行数据属性的劫持 { username: 'LastStarDust' } observable(obj) { // obj为空或者不是对象, 不做任何操作 const isEmpty = !obj || typeof obj !== 'object' if(isEmpty) { return } // ['username'] const keys = Object.keys(obj) keys.forEach(key => { // 如果属性值是对象,递归调用 let val = obj[key] if(typeof val === 'object') { this.observable(val) } // this.defineReactive(this.$data, 'username', 'LastStarDust') this.defineReactive(obj, key, val) }) return obj } // 数据劫持,修改属性的get和set方法 defineReactive(obj, key, val) { const dep = new Dep() Object.defineProperty(obj, key, { enumerable: true, configurable: true, get() { // 会在初始化时, 触发属性get()方法,来到这里Dep.target有值,将其作为订阅者存储起来,在触发属性的set()方法时,调用notify方法 if(Dep.target) { dep.add(Dep.target) } console.log(`取出${key}属性值: 值为${val}`) return val }, set(newVal) { // 没有发生变化, 不做更新 if(newVal === val) { return } console.log(`更新属性${key}的值为: ${newVal}`) val = newVal dep.notify() } }) } } // 定义消息订阅器 class Dep { // 静态属性 Dep.target,这是一个全局唯一 的Watcher,因为在同一时间只能有一个全局的 Watcher static target = null constructor() { // 存储订阅者 this.subs = [] } // 添加订阅者 add(sub) { this.subs.push(sub) } // 通知 notify() { this.subs.forEach(sub => { // 调用订阅者的update方法 sub.update() }) } } // 定义订阅者类 class Wather { constructor(vm, exp, cb) { this.vm = vm // vm实例 this.exp = exp // 指令对应的字符串值, 如v-model="username", exp相当于"username" this.cb = cb // 回到函数 更新视图时调用 this.value = this.get() // 将自己添加到消息订阅器Dep中 } get() { // 将当前订阅者作为全局唯一的Wather,添加到Dep.target上 Dep.target = this // 获取数据,触发属性的getter方法 const value = this.vm.$data[this.exp] // 在执行添加到消息订阅Dep后, 重置Dep.target Dep.target = null return value } // 执行更新 update() { this.run() } run() { // 从Model模型中取出属性值 const newVal = this.vm.$data[this.exp] const oldVal = this.value if(newVal === oldVal) { return false } // 执行回调函数, 将vm实例,新值,旧值传递过去 this.cb.call(this.vm, newVal, oldVal) } } // 定义解析器 // 解析指令,替换模板数据,初始视图 // 模板的指令绑定更新函数, 数据更新时, 更新视图 class Compile { constructor(el, vm) { this.el = el this.vm = vm this.init(this.el) } init(el) { this.compileEle(el) } compileEle(ele) { const nodes = ele.children for(const node of nodes) { if(node.children && node.children.length !== 0) { // 递归调用, 编译子节点 this.compileEle(node) } // 指令时v-model并且是标签是输入标签 const hasVmodel = node.hasAttribute('v-model') const isInputTag = ['INPUT', 'TEXTAREA'].indexOf(node.tagName) !== -1 if(hasVmodel && isInputTag) { const exp = node.getAttribute('v-model') const val = this.vm.$data[exp] const attr = 'value' // 初次模型值推到视图层,初始化视图 this.modelToView(node, val, attr) // 实例化一个订阅者, 将更新函数绑定到订阅者上, 未来数据更新,可以更新视图 new Wather(this.vm, exp, (newVal)=> { this.modelToView(node, newVal, attr) }) // 监听视图的改变 node.addEventListener('input', (e) => { this.viewToModel(exp, e.target.value) }) } if(node.hasAttribute('v-bind')) { const exp = node.getAttribute('v-bind') const val = this.vm.$data[exp] const attr = 'innerHTML' // 初次模型值推到视图层,初始化视图 this.modelToView(node, val, attr) // 实例化一个订阅者, 将更新函数绑定到订阅者上, 未来数据更新,可以更新视图 new Wather(this.vm, exp, (newVal)=> { this.modelToView(node, newVal, attr) }) } } } // 将模型值更新到视图 modelToView(node, val, attr) { node[attr] = val } // 将视图值更新到模型上 viewToModel(exp, val) { this.vm.$data[exp] = val } } const myVm = new MyVue({ el: '#app', data: { username: 'LastStarDust' } }) // console.log(Dep.target) </script> </body> </html>例
免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:mmqy2019@163.com进行举报,并提供相关证据,查实之后,将立刻删除涉嫌侵权内容。
猜你喜欢
这篇文章主要给大家介绍keep-alive,keep-alive的设计初衷就是为了保持组件的状态,避免组件的重复渲染,那么keep-alive是如何保持组件状态的呢?为什么keep-alive可以直接使用?下面我们来具体的了解看看。
vue刷新数据丢失的情况怎样解决?对于这个问题,相信不少朋友都又遇到,也就是进行F5页面刷新的时候,页面的数据会丢失的情况,那么我们有什么解决方法呢?下面我们了解看看。
JavaScript 函数提供两个接口实现与外界的交互,其中参数作为入口,接收外界信息;返回值作为出口,把运算结果反馈给外界。下面本篇文章带大家了解一下JavaScript函数返回值,浅析下return语句的用法,希望对大家有所帮助!
这篇文章主要为大家详细介绍了jQuery实现表单验证功能,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
这篇文章介绍了Node.js进程管理之进程集群,文中通过示例代码介绍的非常详细。对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
成为群英会员,开启智能安全云计算之旅
立即注册Copyright © QY Network Company Ltd. All Rights Reserved. 2003-2020 群英 版权所有
增值电信经营许可证 : B1.B2-20140078 粤ICP备09006778号 域名注册商资质 粤 D3.1-20240008