vue双向绑定及观察者模式详解


Posted in Javascript onMarch 19, 2019

在Vue中,使用了Object.defineProterty()这个函数来实现双向绑定,这也就是为什么Vue不兼容IE8

1 响应式原理

让我们先从相应式原理开始。我们可以通过Object.defineProterty()来自定义Object的getter和setter 从而达到我们的目的。

代码如下

function observe(value, cb) {
 Object.keys(value).forEach((key) => defineReactive(value, key, value[key] , cb))
}

function defineReactive (obj, key, val, cb) {
 Object.defineProperty(obj, key, {
  enumerable: true,
  configurable: true,
  get: ()=>{
   /*....依赖收集等....*/
   /*Github:https://github.com/answershuto*/
   return val
  },
  set:newVal=> {
   val = newVal;
   cb();/*订阅者收到消息的回调*/
  }
 })
}

class Vue {
 constructor(options) {
  this._data = options.data;
  observe(this._data, options.render)
 }
}

let app = new Vue({
 el: '#app',
 data: {
  text: 'text',
  text2: 'text2'
 },
 render(){
  console.log("render");
 }
})

通过observe函数对app.data上的每一个key和value都设定getter和setter。当value改变的时候触发setter,就会触发render这个函数。响应式的目的就达成,如果是视图更新的话我们通过监听dom的input事件来触发数据更新
但是现在我们只有在改变vue._data.text的时候才会触发他们的setter,但是我想偷懒,只改变vue.text就能触发到setter怎么做呢?

我们使用代理的方法

_proxy.call(this, options.data);/*构造函数中*/

/*代理*/
function _proxy (data) {
 const that = this;
 Object.keys(data).forEach(key => {
  Object.defineProperty(that, key, {
   configurable: true,
   enumerable: true,
   get: function proxyGetter () {
    return that._data[key];
   },
   set: function proxySetter (val) {
    that._data[key] = val;
   }
  })
 });
}

依赖收集

让我们再来看看下面的代码

new Vue({
 template: 
  `<div>
   <span>text1:</span> {{text1}}
   <span>text2:</span> {{text2}}
  <div>`,
 data: {
  text1: 'text1',
  text2: 'text2',
  text3: 'text3'
 }
});

当你的text3变化的时候,实际上text3并没有被渲染,但是也会触发一次render函数,这显然是不对的。所以我们需要收集依赖。

我们只需要在初始化的时候渲染一遍,那所有渲染所依赖的数据都会被触发getter,这时候我们只要把这个数据放到一个列表里就好啦!

我们先来认识一下Dep(dependencies)这个类,下图是一个最简单的Dep类。我们可以把他理解为发布者(这点很重要!!)

class Dep {
 constructor () {
  this.subs = [];
 }

 addSub (sub: Watcher) {
  this.subs.push(sub)
 }

 removeSub (sub: Watcher) {
  remove(this.subs, sub)
 }
 /*Github:https://github.com/answershuto*/
 notify () {
  // stabilize the subscriber list first
  const subs = this.subs.slice()
  for (let i = 0, l = subs.length; i < l; i++) {
   subs[i].update()
  }
 }
}
function remove (arr, item) {
 if (arr.length) {
  const index = arr.indexOf(item)
  if (index > -1) {
   return arr.splice(index, 1)
 }
}

我们每次触发getter的时候,只要把触发的对象放到dep.sub里面就好啦!
但是现在问题来了,我们用什么来装这个触发的'对象',也可以说式订阅者呢?

我们使用Watcher这个类

class Watcher {
 constructor (vm, expOrFn, cb, options) {
  this.cb = cb;
  this.vm = vm;

  /*在这里将观察者本身赋值给全局的target,只有被target标记过的才会进行依赖收集*/
  Dep.target = this;
  /*Github:https://github.com/answershuto*/
  /*触发渲染操作进行依赖收集*/
  this.cb.call(this.vm);
 }

 update () {
  this.cb.call(this.vm);
 }
}

vm即是vue实例, expOrFn就是{{a+b}}里面的a+b, cb就是回调函数就是return a+b, options是一些配置项。

Vue在第一次渲染列表的时候如果碰到{{xxx}}这样的表达式,就会new Watcher()。解析里面的函数,然后把当前的watcher实例赋给Dep.target(Dep.target是全局的,一次性只能有一个存在,因为Vue一次只处理一个依赖)。然后执行回调函数。(这里看似是执行回调函数渲染,其实又触发了一次getter,然后就会把当前的依赖添加到sub里去)

接下来开始依赖收集

class Vue {
 constructor(options) {
  this._data = options.data;
  observer(this._data, options.render);
  let watcher = new Watcher(this, );
 }
}

function defineReactive (obj, key, val, cb) {
 /*在闭包内存储一个Dep对象*/
 const dep = new Dep();

 Object.defineProperty(obj, key, {
  enumerable: true,
  configurable: true,
  get: ()=>{
   if (Dep.target) {
    /*Watcher对象存在全局的Dep.target中*/
    dep.addSub(Dep.target);
   }
  },
  set:newVal=> {
   /*只有之前addSub中的函数才会触发*/
   dep.notify();
  }
 })
}

Dep.target = null; //防止依赖重复添加

这儿我们通过示例来讲解

<template>
 <div>
 {{a+b}}
 </div>
 <div>
 {{a-c}}
 </div>
</template>

<script>
let app = new Vue( {
 data :{
  a: 1,
  b: 2,
  c: 3
 }
 })

我们编译到{{a+b}},会去实例化一个对应的Watcher对象,Watcher的构造函数中有这么一句

this.cb.call(this.vm);this.cb指的是function(){return a+b};this.vm指的是这个vue对象,这样就会触发vue.a和vue.b的getter方法,a,b都有自己的dep对象,我们通过Dep.target将这个Watcher对象就加到dep的subs数组中去了,当我们变更a或者b是就会触发setter,进而触发subs数组中的update方法,视图中的a+b就会更新

有个小知识点:我们新建一个属性对象时必须通过Vue.set的方法去实现,而不能直接通过=实现,这样会检测不到,因为我们在初始化时就通过defineProperty重构了这个对象属性的getter和setter方法,新建的属性则没有所以不会被检测到

下图为Vue框架在数据初始化中使用观察者模式的示意图:

vue双向绑定及观察者模式详解

以上所述是小编给大家介绍的vue双向绑定及观察者模式详解整合,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对三水点靠木网站的支持!

Javascript 相关文章推荐
推荐:极酷右键菜单
Nov 29 Javascript
上传图片预览JS脚本 Input file图片预览的实现示例
Oct 23 Javascript
jQuery中focus事件用法实例
Dec 26 Javascript
JS实现弹出浮动窗口(支持鼠标拖动和关闭)实例详解
Aug 06 Javascript
JS设置下拉列表框当前所选值的方法
Dec 22 Javascript
原生JS实现网络彩票投注效果
Sep 25 Javascript
JavaScript函数中的this四种绑定形式
Aug 15 Javascript
详解redux异步操作实践
Aug 15 Javascript
原生JS实现旋转轮播图+文字内容切换效果【附源码】
Sep 29 Javascript
vue+element UI实现树形表格带复选框的示例代码
Apr 16 Javascript
详解webpack的文件监听实现(热更新)
Sep 11 Javascript
解决vue-router 嵌套路由没反应的问题
Sep 22 Javascript
Vue2.0+Vux搭建一个完整的移动webApp项目的示例
Mar 19 #Javascript
在vue中使用G2图表的示例代码
Mar 19 #Javascript
Three.js中矩阵和向量的使用教程
Mar 19 #Javascript
vue+iview动态渲染表格详解
Mar 19 #Javascript
浅谈vue加载优化策略
Mar 19 #Javascript
Vue中Table组件Select的勾选和取消勾选事件详解
Mar 19 #Javascript
详解js加减乘除精确计算
Mar 19 #Javascript
You might like
DedeCms模板安装/制作概述
2007/03/11 PHP
PHP中str_replace函数使用小结
2008/10/11 PHP
PHP 面向对象程序设计(oop)学习笔记(一) - 抽象类、对象接口、instanceof 和契约式编程
2014/06/12 PHP
PHP/ThinkPHP实现批量打包下载文件的方法示例
2017/07/31 PHP
js控制table合并具体实现
2014/02/20 Javascript
Node.js与PHP、Python的字符处理性能对比
2014/07/06 Javascript
js实现对table的增加行和删除行的操作方法
2016/10/13 Javascript
JS判断是否为JSON对象及是否存在某字段的方法(推荐)
2016/11/29 Javascript
jQuery中的select操作详解
2016/11/29 Javascript
Angular.js与node.js项目里用cookie校验账户登录详解
2017/02/22 Javascript
js实现下拉菜单效果
2017/03/01 Javascript
AngularJS使用拦截器实现的loading功能完整实例
2017/05/17 Javascript
使用JS编写的随机抽取号码的小程序
2017/08/11 Javascript
微信小程序判断用户是否需要再次授权获取个人信息
2019/07/18 Javascript
js实现登录拖拽窗口
2020/02/10 Javascript
javascript设计模式 ? 原型模式原理与应用实例分析
2020/04/10 Javascript
vue点击标签切换选中及互相排斥操作
2020/07/17 Javascript
原生JS实现pc端轮播图效果
2020/12/21 Javascript
python使用正则表达式的search()函数实现指定位置搜索功能
2017/11/10 Python
Selenium鼠标与键盘事件常用操作方法示例
2018/08/13 Python
python模拟菜刀反弹shell绕过限制【推荐】
2019/06/25 Python
Pycharm运行加载文本出现错误的解决方法
2019/06/27 Python
使用python 写一个静态服务(实战)
2019/06/28 Python
python datetime中strptime用法详解
2019/08/29 Python
python实现简单日志记录库glog的使用
2019/12/13 Python
使用Django搭建网站实现商品分页功能
2020/05/22 Python
python实现马丁策略回测3000只股票的实例代码
2021/01/22 Python
H5新属性audio音频和video视频的控制详解(推荐)
2016/12/09 HTML / CSS
高校毕业生登记表自我鉴定
2013/11/03 职场文书
金融行业务员的自我评价
2013/12/13 职场文书
业务员的岗位职责
2014/03/15 职场文书
卫校毕业生个人自我鉴定
2014/04/28 职场文书
大学生上课迟到检讨书
2014/10/15 职场文书
《鲁滨逊漂流记》之六读后感(4篇)
2019/09/29 职场文书
浅析MongoDB之安全认证
2021/06/26 MongoDB
Python find()、rfind()方法及作用
2022/12/24 Python