详解VueJS 数据驱动和依赖追踪分析


Posted in Javascript onJuly 26, 2017

之前关于 Vue 数据绑定原理的一点分析,最近需要回顾,就顺便发到随笔上了

在之前实现一个自己的Mvvm中,用 setter 来观测model,将界面上所有的 viewModel 绑定到 model 上。 当model改变,更新所有的viewModel,将新值渲染到界面上 。同时监听界面上通过v-model 绑定的所有 input,并通过 addEventListener事件将新值更新到 model 上,以此来完成双向绑定 。

但是那段程序除了用来理解 defineProperty,其它一文不值。

  1. 没有编译节点 。
  2. 没有处理表达式依赖 。

这里我将解决表达式依赖这个问题,vue 模板的编译我会在下一节介绍 。

为数据定义 getter & setter

class Observer {
  constructor(data) {
    this._data = data;
    this.walk(this._data);
  }

  walk(data) {
    Object.keys(data).forEach((key) => { this.defineRective(data, key, data[key]) })
  };
  defineRective(vm, key, value) {
    var self = this;
    if (value && typeof value === "object") {
      this.walk(value);
    }
    Object.defineProperty(vm, key, {
      get: function() {
        return value;
      },
      set: function(newVal) {
        if (value != newVal) {
          if (newVal && typeof newVal === "object") {
            self.walk(newVal);
          }
          value = newVal;
        }
      }
    })
  }
}

module.exports = Observer;

这样,就为每个属性添加了 getter 和 setter ,当属性是一个对象,那么就递归添加。

一旦获取属性值或者为属性赋值就会触发 get 或 set ,当触发了 set,即model变化,就可以发布一个消息,通知所有viewModel 更新。

defineRective(vm, key, value) {
  // 将这个属性的依赖表达式存储在闭包中。
  var dep = new Dep();
  var self = this;
  if (value && typeof value === "object") {
    this.walk(value);
  }
  Object.defineProperty(vm, key, {
    get: function() {
      return value;
    },
    set: function(newVal) {
      if (value != newVal) {
        if (newVal && typeof newVal === "object") {
          self.walk(newVal);
        }
        value = newVal;
        // 通知所有的 viewModel 更新
        dep.notify();
      }
    }
  })
}

那么怎么定义 Dep 呢??

class Dep {
  constructor() {
    // 依赖列表
    this.dependences = [];
  }
  // 添加依赖
  addDep(watcher) {
    if (watcher) {
      this.dependences.push(watcher);
    }
  }
  // 通知所有依赖更新
  notify() {
    this.dependences.forEach((watcher) => {
      watcher.update();
    })
  }
}

module.exports = Dep;

这里的每个依赖就是一个Watcher 。

看看如何定义 Watcher

这里每一个 Watcher 都会有一个唯一的id号,它拥有一个表达式和一个回调函数 。

比如 表达式 a +b ; 会在get 计算时 访问 a 与 b , 由于 JavaScript是单线程,任一时刻只有一处JavaScript代码在执行, 用Dep.target 作为一个全局变量来表示当前 Watcher 的表达式,然后通过 compute 访问 a ,b ,触发 a 与b 的getter,在 getter 里面将 Dep.target 添加为依赖 。

一旦 a 与 b 的set 触发,调用 update 函数,更新依赖的值 。

var uid = 0;
class Watcher {
  constructor(viewModel, exp, callback) {
    this.viewModel = viewModel;
    this.id = uid++;
    this.exp = exp;
    this.callback = callback;
    this.oldValue = "";
    this.update();
  }

  get() {
    Dep.target = this;
    var res = this.compute(this.viewModel, this.exp);
    Dep.target = null;
    return res;
  }

  update() {
    var newValue = this.get();
    if (this.oldValue === newValue) {
      return;
    }
    // callback 里进行Dom 的更新操作
    this.callback(newValue, this.oldValue);
    this.oldValue = newValue;
  }

  compute(viewModel, exp) {
    var res = replaceWith(viewModel, exp);
    return res;
  }
}

module.exports = Watcher;

由于当前表达式需要在 当前的model下面执行,所以 采用replaceWith 函数来代替 with ,具体可以查看另一篇随笔javascript 中 with 的替代语法。

通过get 添加依赖

Object.defineProperty(vm, key, {
  get: function() {
    var watcher = Dep.target;
    if (watcher && !dep.dependences[watcher.id]) {
      dep.addDep(watcher);
    }
    return value;
  },
  set: function(newVal) {
    if (value != newVal) {
      if (newVal && typeof newVal === "object") {
        self.walk(newVal);
      }
      value = newVal;
      dep.notify();
    }
  }
})

这种添加依赖的方式实在太巧妙了 。

 这里我画了一个图来描述

详解VueJS 数据驱动和依赖追踪分析

最后通过一段代码简单测试一下

const Observer = require('./Observer.js');
const Watcher = require('./watcher.js');
var data = {
  a: 10,
  b: {
    c: 5,
    d: {
      e: 20,
    }
  }
}

var observe = new Observer(data);

var watcher = new Watcher(data, "a+b.c", function(newValue, oldValue) {
  console.log("new value is " + newValue);
  console.log("oldValue is " + oldValue);
});
console.log("\r\n");
console.log("a has changed to 50,then the expr should has value 55");
data.a = 50;

console.log("\r\n");
console.log("b.c has changed to 50,then the expr should has value 122");
data.b.c = 72;;

console.log("\r\n");
console.log("b.c has reseted an object,then the expr should has value 80");
data.b = { c: 30 }

详解VueJS 数据驱动和依赖追踪分析

OK 大功告成

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持三水点靠木。

Javascript 相关文章推荐
JavaScript Function函数类型介绍
Apr 08 Javascript
jQuery实现图片向左向右切换效果的简单实例
May 18 Javascript
js实现将json数组显示前台table中
Jan 10 Javascript
AngularJs 利用百度地图API 定位当前位置 获取地址信息
Jan 18 Javascript
JS实现动态修改table及合并单元格的方法示例
Feb 20 Javascript
jquery 禁止鼠标右键并监听右键事件
Apr 27 jQuery
jQuery插件imgAreaSelect基础讲解
May 26 jQuery
jQuery滚动条美化插件nicescroll简单用法示例
Apr 18 jQuery
详解webpack的proxyTable无效的解决方案
Jun 15 Javascript
10分钟彻底搞懂Http的强制缓存和协商缓存(小结)
Aug 30 Javascript
VUEX-action可以修改state吗
Nov 19 Javascript
微信小程序多列表渲染数据开关互不影响的实现
Jun 05 Javascript
前端主流框架vue学习笔记第二篇
Jul 26 #Javascript
浅谈vue.js中v-for循环渲染
Jul 26 #Javascript
前端主流框架vue学习笔记第一篇
Jul 26 #Javascript
关于vue.js组件数据流的问题
Jul 26 #Javascript
Vue.js弹出模态框组件开发的示例代码
Jul 26 #Javascript
VueJs单页应用实现微信网页授权及微信分享功能示例
Jul 26 #Javascript
node实现简单的反向代理服务器
Jul 26 #Javascript
You might like
vs中通过剪切板循环来循环粘贴不同内容
2011/04/30 PHP
PHP显示今天、今月、上月、今年的起点/终点时间戳的代码
2011/05/25 PHP
调整PHP的性能
2013/10/30 PHP
Drupal读取Excel并导入数据库实例
2014/03/02 PHP
CI操作cookie的方法分析(基于helper类库)
2016/03/28 PHP
基于php流程控制语句和循环控制语句(讲解)
2017/10/23 PHP
jQuery对象与DOM对象之间的转换方法
2010/04/15 Javascript
五个jQuery图片画廊插件 推荐
2011/05/12 Javascript
Jquery中的CheckBox、RadioButton、DropDownList的取值赋值实现代码
2011/10/12 Javascript
jquery form 隐藏的input 选择
2014/04/29 Javascript
javascript实现了照片拖拽点击置顶的照片墙代码
2015/04/03 Javascript
tuzhu_req.js 实现仿百度图片首页效果
2015/08/11 Javascript
CSS javascript 结合实现悬浮固定菜单效果
2015/08/23 Javascript
学习JavaScript设计模式(链式调用)
2015/11/26 Javascript
vue多级复杂列表展开/折叠及全选/分组全选实现
2018/11/05 Javascript
Vue.js@2.6.10更新内置错误处机制Fundebug同步支持相应错误监控
2019/05/13 Javascript
用原生JS实现爱奇艺首页导航栏代码实例
2019/09/19 Javascript
原生JS实现留言板
2020/03/26 Javascript
[52:40]完美世界DOTA2联赛PWL S2 Magma vs GXR 第一场 11.29
2020/12/02 DOTA
ssh批量登录并执行命令的python实现代码
2012/05/25 Python
Python连接数据库学习之DB-API详解
2017/02/07 Python
Python入门学习指南分享
2018/04/11 Python
Scrapy使用的基本流程与实例讲解
2018/10/21 Python
OpenCV+Python识别车牌和字符分割的实现
2019/01/31 Python
python爬取百度贴吧前1000页内容(requests库面向对象思想实现)
2019/08/10 Python
Pytorch 计算误判率,计算准确率,计算召回率的例子
2020/01/18 Python
tensorboard实现同时显示训练曲线和测试曲线
2020/01/21 Python
python匿名函数lambda原理及实例解析
2020/02/07 Python
python统计函数库scipy.stats的用法解析
2020/02/25 Python
django admin后管定制-显示字段的实例
2020/03/11 Python
保险公司开门红口号
2014/06/21 职场文书
六查六看剖析材料
2014/10/06 职场文书
2015年工程师工作总结
2015/04/30 职场文书
歌咏比赛主持词
2015/06/29 职场文书
windows安装 redis 6.2.6最新步骤详解
2022/04/26 Redis
MySQL深分页问题解决思路
2022/12/24 MySQL