详解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 相关文章推荐
Js中sort()方法的用法
Nov 04 Javascript
Javascript 面向对象编程(一) 封装
Aug 28 Javascript
修改或扩展jQuery原生方法的代码实例
Jan 13 Javascript
jQuery插件expander实现图片翻转特效
May 21 Javascript
jquery拖拽效果完整实例(附demo源码下载)
Jan 14 Javascript
深入理解JavaScript中的浮点数
May 18 Javascript
JS获取中文拼音首字母并通过拼音首字母快速查找页面内对应中文内容的方法【附demo源码】
Aug 19 Javascript
深入理解Node.js的HTTP模块
Oct 12 Javascript
Vue.js中数组变动的检测详解
Oct 12 Javascript
微信小程序-小说阅读小程序实例(demo)
Jan 12 Javascript
全选复选框JavaScript编写小结(附代码)
Aug 16 Javascript
jQuery表单元素过滤选择器用法实例分析
Feb 20 jQuery
前端主流框架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使用simplexml_load_file加载XML文件并显示XML的方法
2015/03/19 PHP
在PHP站点的页面上添加Facebook评论插件的实例教程
2016/01/08 PHP
laravel项目利用twemproxy部署redis集群的完整步骤
2018/05/11 PHP
PHP如何通过表单直接提交大文件详解
2019/01/08 PHP
TP5框架实现一次选择多张图片并预览的方法示例
2020/04/04 PHP
二行代码解决全部网页木马
2008/03/28 Javascript
比较详细的关于javascript 解析json的代码
2009/12/16 Javascript
jQuery实现图片放大预览实现原理及代码
2013/09/12 Javascript
使用apply方法实现javascript中的对象继承
2013/12/16 Javascript
javascript基本类型详解
2014/11/28 Javascript
jQuery插件AjaxFileUpload实现ajax文件上传
2016/05/05 Javascript
很棒的js Tab选项卡切换效果
2016/08/30 Javascript
JavaScript 对象详细整理总结
2016/09/29 Javascript
nodejs连接mongodb数据库实现增删改查
2016/12/01 NodeJs
基于zepto.js实现登录界面
2017/10/09 Javascript
基于vue配置axios的方法步骤
2017/11/09 Javascript
小程序自定义日历效果
2018/12/29 Javascript
详解vue的数据劫持以及操作数组的坑
2019/04/18 Javascript
Postman环境变量全局变量使用方法详解
2020/08/13 Javascript
Django添加sitemap的方法示例
2018/08/06 Python
python使用selenium登录QQ邮箱(附带滑动解锁)
2019/01/23 Python
python sklearn库实现简单逻辑回归的实例代码
2019/07/01 Python
Python自动生成代码 使用tkinter图形化操作并生成代码框架
2019/09/18 Python
Django对接支付宝实现支付宝充值金币功能示例
2019/12/17 Python
python3将变量写入SQL语句的实现方式
2020/03/02 Python
莱德杯高尔夫欧洲官方商店:Ryder Cup Shop
2019/08/14 全球购物
优秀求职自荐信怎样写
2013/12/18 职场文书
中专生毕业个人鉴定
2014/02/26 职场文书
重点工程汇报材料
2014/08/27 职场文书
学校元旦晚会开场白
2014/12/14 职场文书
文明班级申报材料
2014/12/24 职场文书
高三英语教学计划
2015/01/23 职场文书
小学六一主持词开场白
2015/05/28 职场文书
2019年个人工作总结范文
2019/03/25 职场文书
SQL SERVER中的流程控制语句
2022/05/25 SQL Server
Linux中文件的基本属性介绍
2022/06/01 Servers