详解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 相关文章推荐
修改jquery.lazyload.js实现页面延迟载入
Dec 22 Javascript
javascript 日期时间 转换的方法
Feb 21 Javascript
导入extjs、jquery 文件时$使用冲突问题解决方法
Jan 14 Javascript
AngularJS入门教程(二):AngularJS模板
Dec 06 Javascript
即将发布的jQuery 3 有哪些新特性
Apr 14 Javascript
浅谈Javascript中的Label语句
Dec 14 Javascript
js读取json文件片段中的数据实例
Mar 09 Javascript
vue2.0结合DataTable插件实现表格动态刷新的方法详解
Mar 17 Javascript
Vue的Flux框架之Vuex状态管理器
Jul 30 Javascript
js定时器实现倒计时效果
Nov 05 Javascript
Vuejs中使用markdown服务器端渲染的示例
Nov 22 Javascript
jQuery列表动态增加和删除的实现方法
Nov 05 jQuery
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 session的锁和并发
2016/01/22 PHP
thinkphp5.0自定义验证规则使用方法
2017/11/16 PHP
Laravel使用原生sql语句并调用的方法
2019/10/09 PHP
js关闭父窗口时关闭子窗口
2013/04/01 Javascript
基于javascript 闭包基础分享
2013/07/10 Javascript
含有CKEditor的表单如何提交
2014/01/09 Javascript
JavaScript的Vue.js库入门学习教程
2016/05/23 Javascript
js控制台输出的方法(详解)
2016/11/26 Javascript
JS函数多个参数默认值指定方法分析
2016/11/28 Javascript
原生JS实现幻灯片
2017/02/22 Javascript
整理关于Bootstrap列表组的慕课笔记
2017/03/29 Javascript
JS与jQuery实现子窗口获取父窗口元素值的方法
2017/04/17 jQuery
JavaScript的继承实现小结
2017/05/07 Javascript
详解Vue-cli 创建的项目如何跨域请求
2017/05/18 Javascript
详细分析单线程JS执行问题
2017/11/22 Javascript
swiper 解决动态加载数据滑动失效的问题
2018/02/26 Javascript
vue实现登录页面的验证码以及验证过程解析(面向新手)
2019/08/02 Javascript
微信小程序 腾讯地图SDK 获取当前地址实现解析
2019/08/12 Javascript
如何利用node转发请求详解
2020/09/17 Javascript
[52:36]VGJ.S vs Serenity 2018国际邀请赛小组赛BO2 第一场 8.19
2018/08/21 DOTA
[01:20:05]DOTA2-DPC中国联赛 正赛 Ehome vs VG BO3 第二场 2月5日
2021/03/11 DOTA
用Python调用win命令行提高工作效率的实例
2019/08/14 Python
Python搭建代理IP池实现存储IP的方法
2019/10/27 Python
如何避免常见的6种HTML5错误用法
2017/11/06 HTML / CSS
比利时网上药店: Drogisterij.net
2017/03/17 全球购物
北美个性化礼品商店:Things Remembered
2018/06/12 全球购物
ParcelABC西班牙:包裹运送和快递服务
2019/12/24 全球购物
出国留学担保书
2014/05/20 职场文书
医学专业毕业生推荐信
2014/07/12 职场文书
道路施工安全责任书
2014/07/24 职场文书
少先队活动总结
2014/08/29 职场文书
小学教师工作总结2015
2015/04/07 职场文书
2015年秘书个人工作总结
2015/04/25 职场文书
赞美教师的句子
2019/09/02 职场文书
python设置 matplotlib 正确显示中文的四种方式
2021/05/10 Python
Python通过loop.run_in_executor执行同步代码 同步变为异步
2022/04/11 Python