vue框架实现双向数据绑定的详细方法操作是什么
Admin 2022-06-11 群英技术资讯 730 次浏览
在这篇文章中,我们来学习一下“vue框架实现双向数据绑定的详细方法操作是什么”的相关知识,下文有详细的讲解,易于大家学习和理解,有需要的朋友可以借鉴参考,下面就请大家跟着小编的思路一起来学习一下吧。主要是通过数据劫持和发布订阅一起实现的
<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进行举报,并提供相关证据,查实之后,将立刻删除涉嫌侵权内容。
猜你喜欢
原型是function对象下的属性,它定义了构造函数的共同祖先,也就是一个父子级的关系,子对象会继承父对象的方法和属性,每个实例对象下都有__proto__属性,通过属性__proto__指向构造函数的原型对象,当到达末端时,返回null,这样一层一层向顶端查找,就形成了原型链
这篇文章主要介绍了在vue中通过render函数给子组件设置ref操作,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧
模板引擎Express支持许多模板引擎,常用的有:haml的实现Hamlhaml.js接替者,同时也是Express的默认模板引擎Jade嵌入JavaScript模板EJS基于CoffeeScript的模板引擎CoffeeKup的NodeJS版本jQuery模板引擎视图渲染(viewrandering)视图的文件名默认需遵循“<name>.<e
这篇文章主要为大家介绍了Vue.js中动态更改svg的相关属性详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
//表示引入http模块varhttp=require('http');//接收两个参数,一个是request和response//request获取url传过来的信息//response给浏览器响应信息http.createServer(function(request,response){ //设置响应头 response.writeHead(200,{'C
成为群英会员,开启智能安全云计算之旅
立即注册Copyright © QY Network Company Ltd. All Rights Reserved. 2003-2020 群英 版权所有
增值电信经营许可证 : B1.B2-20140078 粤ICP备09006778号 域名注册商资质 粤 D3.1-20240008