详解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实现的鼠标链接提示效果生成器代码
Jun 28 Javascript
javascript面向对象之Javascript 继承
May 04 Javascript
JS 各种网页尺寸判断实例方法
Apr 18 Javascript
js 实现菜单左右滚动显示示例介绍
Nov 21 Javascript
将json对象转换为字符串的方法
Feb 20 Javascript
深入理解JavaScript系列(35):设计模式之迭代器模式详解
Mar 03 Javascript
javascript属性访问表达式用法分析
Apr 25 Javascript
JS+CSS实现TreeMenu二级树形菜单完整实例
Sep 18 Javascript
前端开发不得不知的10个最佳ES6特性
Aug 30 Javascript
Vue props用法详解(小结)
Jul 03 Javascript
Vue实现将数据库中带html标签的内容输出(原始HTML(Raw HTML))
Oct 28 Javascript
vue生命周期钩子函数以及触发时机
Apr 26 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
用文本作数据处理
2006/10/09 PHP
一个简单的PHP投票程序源码
2007/03/11 PHP
PHP 全角转半角实现代码
2010/05/16 PHP
PHP如何抛出异常处理错误
2011/03/02 PHP
PHP Beanstalkd消息队列的安装与使用方法实例详解
2020/02/21 PHP
Juqery Html(),append()等方法的Bug解决方法
2010/12/13 Javascript
jquery $.ajax相关用法分享
2012/03/16 Javascript
jquery预加载图片的方法
2015/05/27 Javascript
C++中的string类的用法小结
2015/08/07 Javascript
js实现人民币大写金额形式转换
2016/04/27 Javascript
基于jquery实现弹幕效果
2016/09/29 Javascript
angularjs 中$apply,$digest,$watch详解
2016/10/13 Javascript
解析javascript图片懒加载与预加载的分析总结
2016/10/27 Javascript
javascript 利用arguments实现可变长参数
2016/11/21 Javascript
jQuery is not defined 错误原因与解决方法小结
2017/03/19 Javascript
JavaScript实现换肤功能
2017/09/15 Javascript
Vue 通过自定义指令回顾v-内置指令(小结)
2018/09/03 Javascript
使用jQuery动态设置单选框的选中效果
2018/12/06 jQuery
Vuex modules模式下mapState/mapMutations的操作实例
2019/10/17 Javascript
javascript设计模式 ? 享元模式原理与用法实例分析
2020/04/15 Javascript
vue项目在线上服务器访问失败原因分析
2020/08/14 Javascript
jQuery实现动态向上滚动
2020/12/21 jQuery
[04:45]DOTA2-DPC中国联赛正赛 iG vs LBZS 赛后选手采访
2021/03/11 DOTA
Python中zip()函数用法实例教程
2014/07/31 Python
python实现自动登录
2018/09/17 Python
python实现生成字符串大小写字母和数字的各种组合
2019/01/01 Python
pyqt5 实现 下拉菜单 + 打开文件的示例代码
2019/06/20 Python
使用python接受tgam的脑波数据实例
2020/04/09 Python
kmart凯马特官网:美国最大的打折零售商和全球最大的批发商之一
2016/11/17 全球购物
Harrods英国:世界领先的奢侈品百货商店
2020/09/23 全球购物
三下乡活动方案
2014/01/31 职场文书
法人代表证明书格式
2014/10/01 职场文书
入党自传范文2015
2015/06/26 职场文书
高一语文教学反思
2016/02/16 职场文书
PyTorch梯度裁剪避免训练loss nan的操作
2021/05/24 Python
【2·13】一图读懂中国无线电发展
2022/02/18 无线电