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 相关文章推荐
HTML中事件触发列表与解说
Jul 09 Javascript
基于JQuery的一句话搞定手风琴菜单
Sep 14 Javascript
js nextSibling属性和previousSibling属性概述及使用注意
Feb 16 Javascript
JavaScript控制浏览器全屏及各种浏览器全屏模式的方法、属性和事件
Dec 20 Javascript
webpack配置之后端渲染详解
Oct 26 Javascript
从零开始搭建webpack+react开发环境的详细步骤
May 18 Javascript
在react中使用vuex的示例代码
Jul 30 Javascript
给localStorage设置一个过期时间的方法分享
Nov 06 Javascript
js作用域和作用域链及预解析
Apr 11 Javascript
微信小程序文章详情页跳转案例详解
Jul 09 Javascript
js使用文档就绪函数动态改变页面内容示例【innerHTML、innerText】
Nov 07 Javascript
基于js实现数组相邻元素上移下移
May 19 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
一首老MP3,致敬WAR3经典
2021/03/08 魔兽争霸
thinkPHP的Html模板标签使用方法
2012/11/13 PHP
用php随机生成福彩双色球号码的2种方法
2013/02/04 PHP
ThinkPHP表单令牌错误的相关解决方法分析
2016/05/20 PHP
Jquery ajax不能解析json对象,报Invalid JSON错误的原因和解决方法
2010/03/27 Javascript
javascript中有趣的反柯里化深入分析
2012/12/05 Javascript
jquery map方法使用示例
2014/04/23 Javascript
自制微信公众号一键排版工具
2016/09/22 Javascript
详解js界面跳转与值传递
2016/11/22 Javascript
深入学习 JavaScript中的函数调用
2017/03/23 Javascript
JavaScript实现一个空中避难的小游戏
2017/06/06 Javascript
VueAwesomeSwiper在VUE中的使用以及遇到的一些问题
2018/01/11 Javascript
vue中使用ueditor富文本编辑器
2018/02/08 Javascript
jquery 给动态生成的标签绑定事件的几种方法总结
2018/02/24 jQuery
JavaScript实现的反序列化json字符串操作示例
2018/07/18 Javascript
vue中v-for通过动态绑定class实现触发效果
2018/12/06 Javascript
使用js原生实现年份轮播选择效果实例
2021/01/12 Javascript
[00:32]2018DOTA2亚洲邀请赛Newbee出场
2018/04/03 DOTA
python和ruby,我选谁?
2017/09/13 Python
批量将ppt转换为pdf的Python代码 只要27行!
2018/02/26 Python
python numpy格式化打印的实例
2018/05/14 Python
python 高效去重复 支持GB级别大文件的示例代码
2018/11/08 Python
使用python自动追踪你的快递(物流推送邮箱)
2020/03/17 Python
python实现mean-shift聚类算法
2020/06/10 Python
python 线程的五个状态
2020/09/22 Python
Oakley官网:运动太阳镜、雪镜和服装
2016/09/30 全球购物
消防应急演练方案
2014/02/12 职场文书
董事长助理岗位职责
2014/02/18 职场文书
庆元旦活动总结
2014/07/09 职场文书
中药学专业毕业生推荐信
2014/07/10 职场文书
2014年教师节寄语
2014/08/11 职场文书
2014党委书记四风问题对照检查材料思想汇报
2014/09/22 职场文书
2015年音乐教师个人工作总结
2015/05/20 职场文书
农贸批发市场管理制度
2015/08/07 职场文书
Go语言切片前或中间插入项与内置copy()函数详解
2021/04/27 Golang
纯CSS实现一个简单步骤条的示例代码
2022/07/15 HTML / CSS