详解Vue 事件驱动和依赖追踪


Posted in Javascript onApril 22, 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 。

通过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();
  }
 }
})

这种添加依赖的方式实在太巧妙了 。

这里我画了一个图来描述

详解Vue 事件驱动和依赖追踪

最后通过一段代码简单测试一下

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 }

详解Vue 事件驱动和依赖追踪

OK 大功告成

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持三水点靠木。

Javascript 相关文章推荐
JavaScript去除空格的几种方法
Oct 03 Javascript
图片自动缩小的js代码,用以防止图片撑破页面
Mar 12 Javascript
对象特征检测法判断浏览器对javascript对象的支持
Jul 25 Javascript
基于JQuery实现异步刷新的代码(转载)
Mar 29 Javascript
jQuery aminate方法定位到页面具体位置
Dec 26 Javascript
jquery.uploadifive插件怎么解决上传限制图片或文件大小问题
May 08 jQuery
基于iScroll实现下拉刷新和上滑加载效果
Jul 18 Javascript
vue 设置proxyTable参数进行代理跨域
Apr 09 Javascript
原生js通过一行代码实现简易轮播图
Jun 05 Javascript
Vue+Node服务器查询Mongo数据库及页面数据传递操作实例分析
Dec 20 Javascript
vue 实现setInterval 创建和销毁实例
Jul 21 Javascript
JS实现简易图片自动轮播
Oct 16 Javascript
JS使用cookie实现只出现一次的广告代码效果
Apr 22 #Javascript
利用JS实现简单的瀑布流加载图片效果
Apr 22 #Javascript
jQuery实现百度登录框的动态切换效果
Apr 21 #jQuery
HTML5+Canvas调用手机拍照功能实现图片上传(下)
Apr 21 #Javascript
微信JS-SDK选取手机照片上传功能
Apr 21 #Javascript
HTML5+Canvas调用手机拍照功能实现图片上传(上)
Apr 21 #Javascript
HTML5实现微信拍摄上传照片功能
Apr 21 #Javascript
You might like
PHP substr 截取字符串出现乱码问题解决方法[utf8与gb2312]
2011/12/16 PHP
PHP 加密 Password Hashing API基础知识点
2020/03/02 PHP
tp5.1框架数据库子查询操作实例分析
2020/05/26 PHP
用javascript实现改变TEXTAREA滚动条和按钮的颜色,以及怎样让滚动条变得扁平
2007/04/20 Javascript
关于javascript中this关键字(翻译+自我理解)
2010/10/20 Javascript
jQuery 快速结束当前正在执行的动画
2013/11/20 Javascript
js实现固定显示区域内自动缩放图片的方法
2015/07/18 Javascript
利用HTML5的画布Canvas实现刮刮卡效果
2015/09/06 Javascript
jQuery zclip插件实现跨浏览器复制功能
2015/11/02 Javascript
JavaScript隐式类型转换
2016/03/15 Javascript
深入理解JavaScript中的call、apply、bind方法的区别
2016/05/30 Javascript
BootStrap selectpicker后台动态绑定数据的方法
2017/07/28 Javascript
JavaScript实现飞舞的泡泡效果
2020/02/07 Javascript
[03:45]Newbee战队出征西雅图 决战2016国际邀请赛
2016/08/02 DOTA
[00:59]DOTA2荣耀之路1:Doom is back!weapon X!
2018/05/22 DOTA
Python使用剪切板的方法
2017/06/06 Python
python中Pycharm 输出中文或打印中文乱码现象的解决办法
2017/06/16 Python
Python编写合并字典并实现敏感目录的小脚本
2019/02/26 Python
使用turtle绘制五角星、分形树
2019/10/06 Python
python 实现查询Neo4j多节点的多层关系
2019/12/23 Python
python使用python-pptx删除ppt某页实例
2020/02/14 Python
pyx文件 生成pyd 文件用于 cython调用的实现
2021/03/04 Python
澳大利亚牛仔裤商店:Just Jeans
2016/10/13 全球购物
韩都衣舍天猫官方旗舰店:天猫女装销售总冠军
2017/10/10 全球购物
师范院校学生自荐信范文
2013/12/27 职场文书
商务会议邀请函
2014/01/09 职场文书
大学生职业规划范文:象牙塔生活的四年计划
2014/01/14 职场文书
国际经济与贸易专业大学生职业规划书
2014/03/01 职场文书
爱牙日活动总结
2014/08/29 职场文书
成本会计实训报告
2014/11/05 职场文书
2014年校务公开工作总结
2014/12/18 职场文书
承诺书范本
2015/01/21 职场文书
2015年派出所工作总结
2015/04/24 职场文书
公安干警正风肃纪心得体会
2016/01/15 职场文书
2016廉洁从业学习心得体会
2016/01/19 职场文书
解决mysql的int型主键自增问题
2021/07/15 MySQL