详解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 相关文章推荐
JS仿iGoogle自定义首页模块拖拽特效的方法
Feb 13 Javascript
深入理解JavaScript系列(21):S.O.L.I.D五大原则之接口隔离原则ISP详解
Mar 05 Javascript
jQuery选择器源码解读(四):tokenize方法的Expr.preFilter
Mar 31 Javascript
解决jquery中动态新增的元素节点无法触发事件问题的两种方法
Oct 30 Javascript
ion content 滚动到底部会遮住一部分视图的快速解决方法
Sep 06 Javascript
JavaScript实现前端实时搜索功能
Mar 26 Javascript
Kindeditor单独调用单图上传增加预览功能的实例
Jul 31 Javascript
微信小程序实现倒计时60s获取验证码
Apr 17 Javascript
新手入门带你学习JavaScript引擎运行原理
Jun 24 Javascript
Javascript Worker子线程代码实例
Feb 20 Javascript
js与jquery获取input输入框中的值实例讲解
Feb 27 jQuery
Vue详细的入门笔记
May 10 Vue.js
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扩展框架之Yaf框架的安装与使用
2016/05/18 PHP
Windows上php5.6操作mongodb数据库示例【配置、连接、获取实例】
2019/02/13 PHP
jQuery获取css z-index在各种浏览器中的返回值
2010/09/15 Javascript
jquery获取checkbox的值并post提交
2015/01/14 Javascript
jquery实现简单Tab切换菜单效果
2020/07/17 Javascript
JavaScript如何实现跨域请求
2016/08/05 Javascript
hammer.js实现图片手势放大效果
2017/08/29 Javascript
对存在JavaScript隐式类型转换的四种情况的总结(必看篇)
2017/08/31 Javascript
vue cli使用绝对路径引用图片问题的解决
2017/12/06 Javascript
video.js 实现视频只能后退不能快进的思路详解
2018/08/09 Javascript
详解三种方式解决vue中v-html元素中标签样式
2018/11/22 Javascript
微信小程序 行的删除和增加操作实现详解
2019/09/29 Javascript
[03:23]我的刀塔你不可能这么可爱 第一期金萌萌的故事
2014/06/20 DOTA
零基础写python爬虫之神器正则表达式
2014/11/06 Python
python插入数据到列表的方法
2015/04/30 Python
python字符串string的内置方法实例详解
2018/05/14 Python
Python中pandas dataframe删除一行或一列:drop函数详解
2018/07/03 Python
Python微医挂号网医生数据抓取
2019/01/24 Python
Python除法之传统除法、Floor除法及真除法实例详解
2019/05/23 Python
详解KMP算法以及python如何实现
2020/09/18 Python
Django视图类型总结
2021/02/17 Python
python re模块常见用法例举
2021/03/01 Python
HashMap和Hashtable的区别
2013/05/18 面试题
C#如何允许一个类被继承但是避免这个类的方法被重载?
2015/02/24 面试题
医院工作检讨书范文
2014/02/10 职场文书
销售员个人求职的自我评价
2014/02/10 职场文书
中学生英语演讲稿
2014/04/26 职场文书
2014中考励志标语
2014/06/05 职场文书
团拜会策划方案
2014/06/07 职场文书
四风问题对照检查材料
2014/09/22 职场文书
2015毕业实习推荐信
2015/03/23 职场文书
cf战队宣传语
2015/07/13 职场文书
2015选调生工作总结
2015/07/24 职场文书
Golang二维数组的使用方式
2021/05/28 Golang
Spring Boot 整合 Apache Dubbo的示例代码
2021/07/04 Java/Android
Python Pandas读取Excel日期数据的异常处理方法
2022/02/28 Python