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 相关文章推荐
Extjs学习笔记之八 继承和事件基础
Jan 08 Javascript
JavaScript 判断浏览器是否支持SVG的代码
Mar 21 Javascript
jquery简单实现鼠标经过导航条改变背景图
Dec 17 Javascript
快速解决jquery之get缓存问题的最简单方法介绍
Dec 19 Javascript
JQuery选中checkbox方法代码实例(全选、反选、全不选)
Apr 27 Javascript
Angular.js如何从PHP读取后台数据
Mar 24 Javascript
深入理解Node.js 事件循环和回调函数
Nov 02 Javascript
Js自动截取字符串长度,添加省略号(……)的实现方法
Mar 06 Javascript
浅谈angular2 组件的生命周期钩子
Aug 12 Javascript
vue中keep-alive组件的入门使用教程
Jun 06 Javascript
Vue替代marquee标签超出宽度文字横向滚动效果
Dec 09 Javascript
基于JS正则表达式实现模板数据动态渲染(实现思路详解)
Mar 07 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
php设计模式之适配器模式实例分析【星际争霸游戏案例】
2020/04/07 PHP
javascript textarea光标定位方法(兼容IE和FF)
2011/03/12 Javascript
js添加table的行和列 具体实现方法
2013/07/22 Javascript
jQuery.extend 函数及用法详细
2015/09/06 Javascript
详解JavaScript的表达式与运算符
2015/11/30 Javascript
JavaScript的removeChild()函数用法详解
2015/12/27 Javascript
JS中实现函数return多个返回值的实例
2017/02/21 Javascript
微信小程序云开发使用方法新手初体验
2019/05/16 Javascript
Python实现类继承实例
2014/07/04 Python
Python中不同进制互相转换(二进制、八进制、十进制和十六进制)
2015/04/05 Python
在Python程序中操作文件之isatty()方法的使用教程
2015/05/24 Python
python实现将html表格转换成CSV文件的方法
2015/06/28 Python
Python编程实现蚁群算法详解
2017/11/13 Python
pandas的唯一值、值计数以及成员资格的示例
2018/07/25 Python
Python实现多线程的两种方式分析
2018/08/29 Python
Python神奇的内置函数locals的实例讲解
2019/02/22 Python
Django处理多用户类型的方法介绍
2019/05/18 Python
python中selenium操作下拉滚动条的几种方法汇总
2019/07/14 Python
python调用其他文件函数或类的示例
2019/07/16 Python
在VS2017中用C#调用python脚本的实现
2019/07/31 Python
python实现对图片进行旋转,放缩,裁剪的功能
2019/08/07 Python
python 并发编程 多路复用IO模型详解
2019/08/20 Python
记录一下scrapy中settings的一些配置小结
2020/09/28 Python
pytorch 实现L2和L1正则化regularization的操作
2021/03/03 Python
使用html5+css3来实现slider切换效果告别javascript+css
2013/01/08 HTML / CSS
荷兰网上鞋店:Ziengs.nl
2017/01/02 全球购物
美国正版电视节目和电影在线观看:Hulu
2018/05/24 全球购物
俄罗斯在线服装店:STOLNIK
2021/03/07 全球购物
《母亲的恩情》教学反思
2014/02/13 职场文书
cf收人广告词
2014/03/14 职场文书
元旦促销方案
2014/03/15 职场文书
超市开店计划书
2014/04/26 职场文书
检察机关个人对照检查材料
2014/09/15 职场文书
挂职锻炼工作总结2015
2015/05/28 职场文书
防溺水安全教育主题班会
2015/08/12 职场文书
2016年圣诞节义工活动总结
2016/04/01 职场文书