视频1 视频21 视频41 视频61 视频文章1 视频文章21 视频文章41 视频文章61 推荐1 推荐3 推荐5 推荐7 推荐9 推荐11 推荐13 推荐15 推荐17 推荐19 推荐21 推荐23 推荐25 推荐27 推荐29 推荐31 推荐33 推荐35 推荐37 推荐39 推荐41 推荐43 推荐45 推荐47 推荐49 关键词1 关键词101 关键词201 关键词301 关键词401 关键词501 关键词601 关键词701 关键词801 关键词901 关键词1001 关键词1101 关键词1201 关键词1301 关键词1401 关键词1501 关键词1601 关键词1701 关键词1801 关键词1901 视频扩展1 视频扩展6 视频扩展11 视频扩展16 文章1 文章201 文章401 文章601 文章801 文章1001 资讯1 资讯501 资讯1001 资讯1501 标签1 标签501 标签1001 关键词1 关键词501 关键词1001 关键词1501 专题2001
vue 双向数据绑定的实现学习之的实现方法
2020-11-27 22:03:15 责编:小采
文档


提到了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 双向数据绑定的实现学习之的实现方法,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对脚本之家网站的支持!

下载本文
显示全文
专题