详解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 相关文章推荐
IE8下关于querySelectorAll()的问题
May 13 Javascript
window.opener用法和用途实例介绍
Aug 19 Javascript
jquery实现图片放大镜功能
Nov 23 Javascript
js 连续赋值的简单实现
Jun 13 Javascript
文本框只能输入数字的实现方法(兼容IE火狐)
Jun 25 Javascript
AngularJS基础 ng-cut 指令介绍及简单示例
Aug 01 Javascript
AngularJS实现标签页的两种方式
Sep 05 Javascript
微信小程序录音与播放录音功能
Dec 25 Javascript
微信小程序项目总结之点赞 删除列表 分享功能
Jun 25 Javascript
javaScript实现游戏倒计时功能
Nov 17 Javascript
关于Vue中axios的封装实例详解
Oct 20 Javascript
VUE递归树形实现多级列表
Jul 15 Vue.js
前端主流框架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
win平台安装配置Nginx+php+mysql 环境
2016/01/12 PHP
php验证身份证号码正确性的函数
2016/07/20 PHP
PHP pthreads v3下同步处理synchronized用法示例
2020/02/21 PHP
通过jquery实现tab标签浏览效果
2007/02/20 Javascript
基于Jquery的动态添加控件并取值的实现代码
2010/09/24 Javascript
Javascript 中的 call 和 apply使用介绍
2012/02/22 Javascript
javascript 获取网页标题代码实例
2014/01/22 Javascript
ECharts仪表盘实例代码(附源码下载)
2016/02/18 Javascript
jQuery实现的省市县三级联动菜单效果完整实例
2016/08/01 Javascript
AngularJS 自定义过滤器详解及实例代码
2016/09/14 Javascript
JS实现数组去重复值的方法示例
2017/02/18 Javascript
jquery操作select取值赋值与设置选中实例
2017/02/28 Javascript
D3.js进阶系列之CSV表格文件的读取详解
2017/06/06 Javascript
webpack+vue-cil中proxyTable处理跨域的方法
2018/07/20 Javascript
Vue项目History模式404问题解决方法
2018/10/31 Javascript
详解ES6 Fetch API HTTP请求实用指南
2018/11/14 Javascript
VUE v-model表单数据双向绑定完整示例
2019/01/21 Javascript
Vue源码学习之关于对Array的数据侦听实现
2019/04/23 Javascript
简单了解JavaScript sort方法
2019/11/25 Javascript
vue 组件销毁并重置的实现
2020/01/13 Javascript
vue cli3适配所有端方案的实现
2020/04/13 Javascript
Python实现二维有序数组查找的方法
2016/04/27 Python
python 中if else 语句的作用及示例代码
2018/03/05 Python
Python循环中else,break和continue的用法实例详解
2019/07/11 Python
Python+unittest+DDT实现数据驱动测试
2020/11/30 Python
基于Python中Remove函数的用法讨论
2020/12/11 Python
举例讲解Python装饰器
2020/12/24 Python
德国宠物用品、宠物食品及水族馆网上商店:ZooRoyal
2017/07/09 全球购物
奥地利网上书店:Weltbild
2017/07/14 全球购物
Unix/Linux开发面试题
2016/08/16 面试题
创业计划书六个要素
2013/12/26 职场文书
数学教育专业求职信
2014/07/22 职场文书
教师个人教学总结
2015/02/11 职场文书
2015安全保卫工作总结
2015/04/25 职场文书
MySQL 数据库范式化设计理论
2022/04/22 MySQL
zabbix 代理服务器的部署与 zabbix-snmp 监控问题
2022/07/15 Servers