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 相关文章推荐
Javascript 遍历对象中的子对象
Jul 03 Javascript
Javascript 面向对象 继承
May 13 Javascript
js下关于onmouseout、事件冒泡的问题经验小结
Dec 09 Javascript
JavaScript使用shift方法移除素组第一个元素实例分析
Apr 06 Javascript
jQuery移除或禁用html元素点击事件常用方法小结
Feb 10 Javascript
jQuery中extend函数简单用法示例
Oct 11 jQuery
Vue异步组件处理路由组件加载状态的解决方案
Sep 07 Javascript
用node开发并发布一个cli工具的方法步骤
Jan 03 Javascript
小程序如何构建骨架屏
May 29 Javascript
微信小程序自定义组件实现环形进度条
Nov 17 Javascript
layer iframe 设置关闭按钮的方法
Sep 12 Javascript
关于layui时间回显问题的解决方法
Sep 24 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
《Re:从零开始的异世界生活》剧情体验,手游新作定名
2020/04/09 日漫
php数据类型判断函数有哪些
2013/09/23 PHP
thinkphp四种url访问方式详解
2014/11/28 PHP
laravel框架添加数据,显示数据,返回成功值的方法
2019/10/11 PHP
很全的显示阴历(农历)日期的js代码
2009/01/01 Javascript
javascript写的简单的计算器,内容很多,方法实用,推荐
2011/12/29 Javascript
Egret引擎开发指南之发布项目
2014/09/03 Javascript
推荐8款jQuery轻量级树形Tree插件
2014/11/12 Javascript
jquery实现聚光灯效果的方法
2015/02/06 Javascript
利用BootStrap弹出二级对话框的简单实现方法
2016/09/21 Javascript
javascript和jQuery中的AJAX技术详解【包含AJAX各种跨域技术】
2016/12/15 Javascript
jQuery条件分页 代替离线查询(附代码)
2017/08/17 jQuery
使用wxapp-img-loader自定义组件实现微信小程序图片预加载功能
2018/10/18 Javascript
vue favicon设置以及动态修改favicon的方法
2018/12/21 Javascript
Vue实现拖放排序功能的实例代码
2019/07/08 Javascript
JavaScript使用canvas绘制随机验证码
2020/02/17 Javascript
js抽奖转盘实现方法分析
2020/05/16 Javascript
公众号SVG动画交互实战代码
2020/05/31 Javascript
全面解析JavaScript Module模式
2020/07/24 Javascript
解决PyCharm中光标变粗的问题
2017/08/05 Python
PyCharm配置mongo插件的方法
2018/11/30 Python
Python 实现还原已撤回的微信消息
2019/06/18 Python
Django 开发环境配置过程详解
2019/07/18 Python
matlab、python中矩阵的互相导入导出方式
2020/06/01 Python
python numpy库np.percentile用法说明
2020/06/08 Python
python小白学习包管理器pip安装
2020/06/09 Python
Keras设置以及获取权重的实现
2020/06/19 Python
Python中使用Selenium环境安装的方法步骤
2021/02/22 Python
利物浦足球俱乐部官方网上商店:Liverpool FC Official Store
2018/01/13 全球购物
美国韩国化妆品和护肤品购物网站:Beautytap
2018/07/29 全球购物
android面试问题与答案
2016/12/27 面试题
简述进程的启动、终止的方式以及如何进行进程的查看
2014/02/20 面试题
市场营销专业应届生自荐信
2014/06/19 职场文书
2014年大学生职业规划书:未来不是梦,只要勇敢冲!
2014/09/22 职场文书
企业财务经理岗位职责
2015/04/08 职场文书
Redis入门教程详解
2021/08/30 Redis