详解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中SQL语句的应用实现
May 04 Javascript
JavaScript 垃圾回收机制分析
Oct 10 Javascript
jQuery实现鼠标经过图片预览大图效果
Apr 10 Javascript
详细介绍jQuery.outerWidth() 函数具体用法
Jul 20 Javascript
JS HTML5拖拽上传图片预览
Jul 18 Javascript
IE8利用自带的setCapture和releaseCapture解决iframe的拖拽事件方法
Oct 25 Javascript
Vue.js表单控件实践
Oct 27 Javascript
前端自动化开发之Node.js的环境搭建教程
Apr 01 Javascript
使用React手写一个对话框或模态框的方法示例
Apr 25 Javascript
js实现打字小游戏
Dec 17 Javascript
Vue两种组件类型:递归组件和动态组件的用法
Aug 06 Javascript
react antd表格中渲染一张或多张图片的实例
Oct 28 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
PHP新手上路(七)
2006/10/09 PHP
解决phpmyadmin 乱码,支持gb2312和utf-8
2006/11/20 PHP
PHP 5.0对象模型深度探索之类的静态成员
2008/03/27 PHP
header跳转和include包含问题详解
2012/09/08 PHP
浅谈PHP中单引号和双引号到底有啥区别呢?
2015/03/04 PHP
5 cool javascript apps
2007/03/24 Javascript
jquery选择器的选择使用及性能介绍
2013/01/16 Javascript
js实现非常简单的焦点图切换特效实例
2015/05/07 Javascript
JavaScript中Boolean对象的属性解析
2015/10/21 Javascript
第一次接触神奇的Bootstrap表单
2016/07/27 Javascript
js 转json格式的字符串为对象或数组(前后台)的方法
2016/11/02 Javascript
Bootstrap基本组件学习笔记之列表组(11)
2016/12/07 Javascript
JavaScript获取键盘按键的键码(参照表)
2017/01/10 Javascript
微信小程序 下拉菜单的实现
2017/04/06 Javascript
vant(ZanUi)结合async-validator实现表单验证的方法
2018/12/06 Javascript
原生js实现随机点餐效果
2019/12/10 Javascript
pygame学习笔记(5):游戏精灵
2015/04/15 Python
Python编程实现双击更新所有已安装python模块的方法
2017/06/05 Python
python匹配两个短语之间的字符实例
2018/12/25 Python
Python性能分析工具Profile使用实例
2019/11/19 Python
浅谈HTML5新增和废弃的标签
2019/04/28 HTML / CSS
Bench加拿大官方网站:英国城市服装品牌
2017/11/03 全球购物
美国山地自行车、露营、户外装备和服装购物网站:Aventuron
2018/05/05 全球购物
新西兰Bookabach:查找全球度假屋
2020/12/03 全球购物
什么是makefile? 如何编写makefile?
2013/01/02 面试题
人事部专员岗位职责
2014/03/04 职场文书
抗洪救灾先进集体事迹材料
2014/05/26 职场文书
个人四风对照检查材料
2014/09/26 职场文书
群众路线教育党员自我剖析材料
2014/10/06 职场文书
4S店销售内勤岗位职责
2015/04/13 职场文书
教师调动申请报告
2015/05/18 职场文书
个人催款函范文
2015/06/23 职场文书
患者身份识别制度
2015/08/06 职场文书
OpenCV3.3+Python3.6实现图片高斯模糊
2021/05/18 Python
详解Java ES多节点任务的高效分发与收集实现
2021/06/30 Java/Android
Springboot配置suffix指定mvc视图的后缀方法
2021/07/03 Java/Android