详解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中的undefined学习总结
Nov 30 Javascript
js仿微博实现统计字符和本地存储功能
Dec 22 Javascript
BootStrap Table对前台页面表格的支持实例讲解
Dec 22 Javascript
JS常见算法详解
Feb 28 Javascript
Javascript中的async awai的用法
May 17 Javascript
SVG实现时钟效果
Jul 17 Javascript
javascript中关于类型判断的一些疑惑小结
Oct 14 Javascript
微信小程序MUI侧滑导航菜单示例(Popup弹出式,左侧不动,右侧滑动)
Jan 23 Javascript
js实现ATM机存取款功能
Oct 27 Javascript
微信小程序搭建自己的Https服务器
May 02 Javascript
Vue打包后访问静态资源路径问题
Nov 08 Javascript
vue实现员工信息录入功能
Jun 11 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 Image Resize图片大小调整的函数代码
2011/01/17 PHP
php设置session值和cookies的学习示例
2014/03/21 PHP
destoon之一键登录设置
2014/06/21 PHP
完美解决在ThinkPHP控制器中命名空间的问题
2017/05/05 PHP
php图片合成方法(多张图片合成一张)
2017/11/25 PHP
Lumen timezone 时区设置方法(慢了8个小时)
2018/01/20 PHP
json 实例详细说明教程
2009/10/31 Javascript
javascript中节点的最近的相关节点访问方法
2013/03/20 Javascript
javascript 获取浏览器版本
2015/01/21 Javascript
jQuery绑定事件-多种实现方式总结
2016/05/09 Javascript
使用jquery提交form表单并自定义action的方法
2016/05/25 Javascript
关于JS变量和作用域详解
2016/07/28 Javascript
JS基于for语句编写的九九乘法表示例
2018/01/04 Javascript
vue axios 给生产环境和发布环境配置不同的接口地址(推荐)
2018/05/08 Javascript
基于原生js实现判断元素是否有指定class名
2020/07/11 Javascript
Openlayers测量距离与面积的实现方法
2020/09/25 Javascript
python中列表元素连接方法join用法实例
2015/04/07 Python
解读Python中degrees()方法的使用
2015/05/18 Python
Python第三方库xlrd/xlwt的安装与读写Excel表格
2017/01/21 Python
在Django model中设置多个字段联合唯一约束的实例
2019/07/17 Python
利用python、tensorflow、opencv、pyqt5实现人脸实时签到系统
2019/09/25 Python
详解Python的爬虫框架 Scrapy
2020/08/03 Python
HTML5超炫酷粒子效果的进度条的实现示例
2019/08/23 HTML / CSS
鞋子女王塔玛拉·梅隆同名奢侈品牌:Tamara Mellon
2017/11/22 全球购物
大专应届生个人简历的自我评价
2013/10/15 职场文书
中学生社会实践活动总结
2014/07/03 职场文书
建议书格式
2015/02/04 职场文书
升职自我推荐信范文
2015/03/25 职场文书
承诺书范本大全
2015/05/04 职场文书
飞越疯人院观后感
2015/06/09 职场文书
学生退学证明
2015/06/23 职场文书
党员理论学习心得体会
2016/01/21 职场文书
python 自动化偷懒的四个实用操作
2021/04/11 Python
图解上海144收音机
2021/04/22 无线电
Selenium浏览器自动化如何上传文件
2022/04/06 Python
CSS的calc函数用法小结
2022/06/25 HTML / CSS