详解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学习阶段性总结(2)--(X)HTML学习
Feb 03 Javascript
javascript attachEvent绑定多个事件执行顺序问题
Oct 20 Javascript
JS代码同步文本框内容的实例方法
Jul 12 Javascript
js获取系统的根路径实现介绍
Sep 08 Javascript
JavaScript通过正则表达式实现表单验证电话号码
Mar 07 Javascript
JavaScript中数组继承的简单示例
Jul 29 Javascript
javascript中eval解析JSON字符串
Feb 27 Javascript
详解Javascript ES6中的箭头函数(Arrow Functions)
Aug 24 Javascript
JavaScript获取select中text值的方法
Feb 13 Javascript
自定义vue组件发布到npm的方法
May 09 Javascript
Koa从零搭建到Api实现项目的搭建方法
Jul 30 Javascript
vue实现购物车功能(商品分类)
Apr 20 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中new static()与new self()的比较
2016/08/19 PHP
Yii框架弹出框功能示例
2017/01/07 PHP
NiftyCube——轻松实现圆角边框
2007/02/20 Javascript
IE7提供XMLHttpRequest对象为兼容
2007/03/08 Javascript
学习ExtJS Panel常用方法
2009/10/07 Javascript
JavaScript异步编程:异步数据收集的具体方法
2013/08/19 Javascript
JavaScript 异常处理 详解
2015/02/06 Javascript
js数组依据下标删除元素
2015/04/14 Javascript
JavaScript仿静态分页实现方法
2015/08/04 Javascript
Angular.js与Bootstrap相结合实现表格分页代码
2016/04/12 Javascript
Node.js实现数据推送
2016/04/14 Javascript
HTML5基于Tomcat 7.0实现WebSocket连接并实现简单的实时聊天
2016/10/31 Javascript
详解vue-cli构建项目反向代理配置
2017/09/07 Javascript
使用layui前端框架弹出form表单以及提交的示例
2019/10/25 Javascript
如何在微信小程序中存setStorage
2019/12/13 Javascript
vue实现一个获取按键展示快捷键效果的Input组件
2021/01/13 Vue.js
Python日期的加减等操作的示例
2017/08/15 Python
TensorFlow神经网络优化策略学习
2018/03/09 Python
详解python使用turtle库来画一朵花
2019/03/21 Python
Django使用模板后无法找到静态资源文件问题解决
2019/07/19 Python
Django 返回json数据的实现示例
2020/03/05 Python
python GUI库图形界面开发之PyQt5结合Qt Designer创建信号与槽的详细方法与实例
2020/03/08 Python
python转化excel数字日期为标准日期操作
2020/07/14 Python
python给视频添加背景音乐并改变音量的具体方法
2020/07/19 Python
Html5之webcoekt播放JPEG图片流
2020/09/22 HTML / CSS
BCBG官网:BCBGMAXAZRIA
2017/12/29 全球购物
关于Java String的一道面试题
2013/09/29 面试题
介绍一下Linux文件的记录形式
2012/04/18 面试题
技校教师求职简历的自我评价
2013/10/20 职场文书
校庆接待方案
2014/03/18 职场文书
幼儿园区域活动总结
2014/05/08 职场文书
体育专业求职信
2014/07/16 职场文书
趣味运动会广播稿
2014/09/13 职场文书
Python基础之Socket通信原理
2021/04/22 Python
Mysql官方性能测试工具mysqlslap的使用简介
2021/05/21 MySQL
《乙女游戏世界对路人角色很不友好》OP主题曲无字幕动画MV公开
2022/04/05 日漫