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中判断一个元素下面是否有内容或者有某个标签的判断代码
Feb 02 Javascript
基于JQuery实现鼠标点击文本框显示隐藏提示文本
Feb 23 Javascript
js中页面的重新加载(当前页面/上级页面)及frame或iframe元素引用介绍
Jan 24 Javascript
基于JavaScript将表单序列化类型的数据转化成对象的处理(允许对象中包含对象)
Dec 28 Javascript
jQuery中数据缓存$.data的用法及源码完全解析
Apr 29 Javascript
angularjs 源码解析之scope
Aug 22 Javascript
js Canvas实现圆形时钟教程
Sep 19 Javascript
Bootstrap CSS布局之按钮
Dec 17 Javascript
angular和BootStrap3实现购物车功能
Jan 25 Javascript
js实现轮播图的两种方式(构造函数、面向对象)
Sep 30 Javascript
js+html获取系统当前时间
Nov 10 Javascript
vue项目tween方法实现返回顶部的示例代码
Mar 02 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 Static关键字实用方法
2010/06/04 PHP
PHP高自定义性安全验证码代码
2011/11/27 PHP
浅析HTTP消息头网页缓存控制以及header常用指令介绍
2013/06/28 PHP
关于url地址传参数时字符串有回车造成页面脚本赋值失败的解决方法
2013/06/28 PHP
在PHP中运行Linux命令并启动SSH服务的例子
2014/06/12 PHP
php实现的任意进制互转类分享
2015/07/07 PHP
php抽象方法和抽象类实例分析
2016/12/07 PHP
js中同步与异步处理的方法和区别总结
2013/12/25 Javascript
JS获取select-option-text_value的方法
2013/12/26 Javascript
jQuery新的事件绑定机制on()示例应用
2014/07/18 Javascript
JavaScript fontsize方法入门实例(按照指定的尺寸来显示字符串)
2014/10/17 Javascript
JavaScript中的console.log()函数详细介绍
2014/12/29 Javascript
js面向对象之公有、私有、静态属性和方法详解
2015/04/17 Javascript
浅谈jQuery中replace()方法
2015/05/13 Javascript
值得分享的轻量级Bootstrap Table表格插件
2016/05/30 Javascript
Bootstrap select实现下拉框多选效果
2016/12/23 Javascript
canvas压缩图片转换成base64格式输出文件流
2017/03/09 Javascript
Three.js实现浏览器变动时进行自适应的方法
2017/09/26 Javascript
js实现经典贪吃蛇小游戏
2020/03/19 Javascript
快速解决Vue、element-ui的resetFields()方法重置表单无效的问题
2020/08/12 Javascript
python中的reduce内建函数使用方法指南
2014/08/31 Python
python类中super()和__init__()的区别
2016/10/18 Python
django ajax发送post请求的两种方法
2020/01/05 Python
pytorch动态网络以及权重共享实例
2020/01/06 Python
HTML5 history新特性pushState、replaceState及两者的区别
2015/12/26 HTML / CSS
巴西男士个人护理产品商店:SHOP4MEN
2017/08/07 全球购物
大学专科求职信
2014/07/02 职场文书
高中生逃课检讨书
2014/10/10 职场文书
家属慰问信
2015/02/14 职场文书
2014年度个人工作总结范文
2015/03/09 职场文书
办公用品质量保证书
2015/05/11 职场文书
小学秋季运动会通讯稿
2015/11/25 职场文书
转变工作作风心得体会
2016/01/23 职场文书
MySQL 时间类型的选择
2021/06/05 MySQL
pandas进行数据输入和输出的方法详解
2022/03/23 Python
Python实现为PDF去除水印的示例代码
2022/04/03 Python