详解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 解析url的search方法
Feb 09 Javascript
Bootstrap每天必学之级联下拉菜单
Mar 27 Javascript
javascript常见数字进制转换实例分析
Apr 21 Javascript
Javascript如何判断数据类型和数组类型
Jun 22 Javascript
BootStrap 模态框实现刷新网页并关闭功能
Jan 04 Javascript
微信小程序引用公共js里的方法的实例详解
Aug 17 Javascript
js将当前时间格式化为 年-月-日 时:分:秒的实现代码
Jan 20 Javascript
详解nuxt路由鉴权(express模板)
Nov 21 Javascript
Vue学习之axios的使用方法实例分析
Jan 06 Javascript
用JS实现选项卡
Mar 23 Javascript
js 执行上下文和作用域的相关总结
Feb 08 Javascript
如何在CocosCreator里画个炫酷的雷达图
Apr 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
preg_match_all使用心得分享
2014/01/31 PHP
apache php mysql开发环境安装教程
2016/07/28 PHP
yii2实现 "上一篇,下一篇" 功能的代码实例
2017/02/04 PHP
laravel框架中路由设置,路由参数和路由命名实例分析
2019/11/23 PHP
YII2框架中behavior行为的理解与使用方法示例
2020/03/13 PHP
一些常用的Javascript函数
2006/12/22 Javascript
JavaScript 代码压缩工具小结
2012/02/27 Javascript
js的压缩及jquery压缩探讨(提高页面加载性能/保护劳动成果)
2013/01/29 Javascript
JS获得浏览器版本和操作系统版本的例子
2014/05/13 Javascript
js实现飞入星星特效代码
2014/10/17 Javascript
jquery插件validation实现验证身份证号等
2015/06/04 Javascript
JavaScript实现广告的关闭与显示效果实例
2015/07/02 Javascript
js实现精确到秒的倒计时效果
2016/05/29 Javascript
js中window.open的参数及注意注意事项
2016/07/06 Javascript
jquery-mobile基础属性与用法详解
2016/11/23 Javascript
Bootstrap CSS布局之按钮
2016/12/17 Javascript
实时监控input框,实现输入框与下拉框联动的实例
2018/01/23 Javascript
浅谈Vue 性能优化之深挖数组
2018/12/11 Javascript
微信小程序扫描二维码获取信息实例详解
2019/05/07 Javascript
vue-socket.io跨域问题有效解决方法
2020/02/11 Javascript
[03:09]2014DOTA2国际邀请赛 Mushi前队友送上祝福
2014/07/12 DOTA
python实现360的字符显示界面
2014/02/21 Python
Python中规范定义命名空间的一些建议
2016/06/04 Python
基于python socketserver框架全面解析
2017/09/21 Python
机器学习python实战之决策树
2017/11/01 Python
Python实现读取json文件到excel表
2017/11/18 Python
Python使用re模块正则提取字符串中括号内的内容示例
2018/06/01 Python
python traceback捕获并打印异常的方法
2018/08/31 Python
Python assert语句的简单使用示例
2019/07/28 Python
python:解析requests返回的response(json格式)说明
2020/04/30 Python
工业自动化专业自荐信范文
2014/04/10 职场文书
2015年小学一年级班主任工作总结
2015/05/21 职场文书
政协常委会议主持词
2015/07/03 职场文书
Mysql8.0递归查询的简单用法示例
2021/08/04 MySQL
Win11 引入 Windows 365 云操作系统,适应疫情期间混合办公模式:启动时直接登录、模
2022/04/06 数码科技
MyBatis核心源码深度剖析SQL语句执行过程
2022/05/20 Java/Android