vue 双向数据绑定的实现学习之监听器的实现方法


Posted in Javascript onNovember 30, 2018

提到了vue实现的基本实现原理:Object.defineProperty() -数据劫持 和 发布订阅者模式(观察者),下面讲的就是数据劫持在代码中的具体实现。

1.先看如何调用vue 双向数据绑定的实现学习之监听器的实现方法

new一个对象,传入我们的参数,这个Myvue ,做了啥?

vue 双向数据绑定的实现学习之监听器的实现方法

上面看到了在实例化一个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 双向数据绑定的实现学习之监听器的实现方法

未完待续,错误之处,敬请指出,共同进步!

下一篇 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 双向数据绑定的实现学习之监听器的实现方法,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对三水点靠木网站的支持!

Javascript 相关文章推荐
Jquery工作常用实例 使用AJAX使网页进行异步更新
Jul 26 Javascript
JavaScript回调(callback)函数概念自我理解及示例
Jul 04 Javascript
js实现表格字段排序
Feb 19 Javascript
jquery左边浮动到一定位置时显示返回顶部按钮
Jun 05 Javascript
js获取当前页面的url网址信息
Jun 12 Javascript
javascript中Number对象的toString()方法分析
Dec 20 Javascript
jQuery中height()方法用法实例
Dec 24 Javascript
jQuery原型属性和原型方法详解
Jul 07 Javascript
JS实现添加,替换,删除节点元素的方法
Jun 30 Javascript
js实现QQ面板拖拽效果(慕课网DOM事件探秘)(全)
Sep 19 Javascript
利用JS判断客户端类型你应该知道的四种方法
Dec 22 Javascript
layui 中select下拉change事件失效的解决方法
Sep 20 Javascript
基于jquery实现九宫格拼图小游戏
Nov 30 #jQuery
微信小程序canvas.drawImage完全显示图片问题的解决
Nov 30 #Javascript
vue 实现左右拖拽元素并且不超过他的父元素的宽度
Nov 30 #Javascript
微信小程序实现保存图片到相册功能
Nov 30 #Javascript
js canvas实现写字动画效果
Nov 30 #Javascript
webpack打包多页面的方法
Nov 30 #Javascript
Element Table的row-class-name无效与动态高亮显示选中行背景色
Nov 30 #Javascript
You might like
第一节--面向对象编程
2006/11/16 PHP
PHP 读取和编写 XML
2014/11/19 PHP
RR vs IO BO3 第二场2.13
2021/03/10 DOTA
javascript删除option选项的多种方法总结
2013/11/22 Javascript
JQuery与Ajax调用新浪API获取短网址的代码
2014/02/07 Javascript
JS判断文本框内容改变事件的简单实例
2014/03/07 Javascript
jquery禁用右键示例
2014/04/28 Javascript
jquery+ajax验证不通过也提交表单问题处理
2014/12/12 Javascript
jquery二级目录选中当前页的css样式
2016/12/08 Javascript
JS实现随机颜色的3种方法与颜色格式的转化
2017/01/05 Javascript
jQuery Validate 相关参数及常用的自定义验证规则
2017/03/06 Javascript
socket.io学习教程之基本应用(二)
2017/04/29 Javascript
深入浅出webpack教程系列_安装与基本打包用法和命令参数详解
2017/09/10 Javascript
react-native中ListView组件点击跳转的方法示例
2017/09/30 Javascript
基于Vue 2.0的模块化前端 UI 组件库小结
2017/12/21 Javascript
vue中Npm run build 根据环境传递参数方法来打包不同域名
2018/03/29 Javascript
JS将网址url转化为JSON格式的方法
2018/07/02 Javascript
layui实现文件或图片上传记录
2018/08/28 Javascript
小程序从手动埋点到自动埋点的实现方法
2019/01/24 Javascript
vue中 this.$set的用法详解
2019/09/06 Javascript
vue父子组件的通信方法(实例详解)
2019/11/10 Javascript
vue 中使用print.js导出pdf操作
2020/11/13 Javascript
Python3.6通过自带的urllib通过get或post方法请求url的实例
2018/05/10 Python
利用Python实现在同一网络中的本地文件共享方法
2018/06/04 Python
django配置连接数据库及原生sql语句的使用方法
2019/03/03 Python
pip/anaconda修改镜像源,加快python模块安装速度的操作
2021/03/04 Python
CSS3 3D立方体效果示例-transform也不过如此
2016/12/05 HTML / CSS
欧洲最大的高尔夫零售商:American Golf
2019/09/02 全球购物
Lancer Skincare官方网站:抗衰老皮肤护理
2020/11/20 全球购物
公共事业管理本科生求职信
2013/10/07 职场文书
员工培训心得体会
2013/12/30 职场文书
庆中秋节主题活动方案
2014/02/03 职场文书
大学秋游活动方案
2014/02/11 职场文书
宿舍标语大全
2014/06/19 职场文书
寒假致家长的一封信
2015/10/10 职场文书
MySQL优化之如何写出高质量sql语句
2021/05/17 MySQL