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 相关文章推荐
如何在Mozilla Gecko 用Javascript加载XSL
Jan 09 Javascript
jquery isType() 类型判断代码
Feb 14 Javascript
firefox下jQuery UI Autocomplete 1.8.*中文输入修正方法
Sep 19 Javascript
JavaScript中创建对象和继承示例解读
Feb 12 Javascript
JavaScript 学习笔记之数据类型
Jan 14 Javascript
js事件处理程序跨浏览器解决方案
Mar 27 Javascript
bootstrap的3级菜单样式,支持母版页保留打开状态实现方法
Nov 10 Javascript
JS仿JQuery选择器功能
Mar 08 Javascript
JS匹配日期和时间的正则表达式示例
May 12 Javascript
js动态设置select下拉菜单的默认选中项实例
Aug 21 Javascript
layui 实现加载动画以及非真实加载进度的方法
Sep 23 Javascript
Vue插槽_特殊特性slot,slot-scope与指令v-slot说明
Sep 04 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
php防攻击代码升级版
2010/12/29 PHP
PHP实现视频文件上传完整实例
2014/08/28 PHP
标准PHP的AES加密算法类
2015/03/12 PHP
php将HTML表格每行每列转为数组实现采集表格数据的方法
2015/04/03 PHP
php设计模式之委托模式
2016/02/13 PHP
ubutu 16.04环境下,PHP与mysql数据库,网页登录验证实例讲解
2017/07/20 PHP
js 显示base64编码的二进制流网页图片
2014/04/04 Javascript
jQuery插件slicebox实现3D动画图片轮播切换特效
2015/04/12 Javascript
JavaScript中的Math.LOG2E属性使用详解
2015/06/14 Javascript
javascript将DOM节点添加到文档的方法实例分析
2015/08/04 Javascript
jquery点击缩略图切换视频播放特效代码分享
2015/09/15 Javascript
JS iFrame加载慢怎么解决
2016/05/13 Javascript
浅谈Vuejs中nextTick()异步更新队列源码解析
2017/12/31 Javascript
Vuex 在Vue 组件中获得Vuex 状态state的方法
2018/08/27 Javascript
微信小程序自定义模态弹窗组件详解
2019/12/24 Javascript
JavaScript代码异常监控实现过程详解
2020/02/17 Javascript
Postman内建变量常用方法实例解析
2020/07/28 Javascript
微信小程序实现电影App导航和轮播
2020/11/30 Javascript
[02:01]大师之路——DOTA2完美大师赛11月论剑上海
2017/11/06 DOTA
[56:56]VG vs LGD 2019国际邀请赛淘汰赛 胜者组 BO3 第一场 8.22
2019/09/05 DOTA
详解Python的迭代器、生成器以及相关的itertools包
2015/04/02 Python
python+django+sql学生信息管理后台开发
2018/01/11 Python
python字典值排序并取出前n个key值的方法
2018/10/17 Python
Python把对应格式的csv文件转换成字典类型存储脚本的方法
2019/02/12 Python
Python+OpenCV实现实时眼动追踪的示例代码
2019/11/11 Python
新手入门学习python Numpy基础操作
2020/03/02 Python
25个CSS3动画按钮和菜单教程分享
2012/10/03 HTML / CSS
Python里面如何拷贝一个对象
2014/02/17 面试题
吸烟检讨书2000字
2014/02/13 职场文书
2014年银行个人工作总结
2014/12/05 职场文书
员工评语范文
2014/12/31 职场文书
课外活动实习计划
2015/01/19 职场文书
师范生教育见习总结
2015/06/23 职场文书
导游词之清晏园
2019/11/22 职场文书
适合后台管理系统开发的12个前端框架(小结)
2021/06/29 Javascript
MySQL系列之十 MySQL事务隔离实现并发控制
2021/07/02 MySQL