详解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知识点三 jquery表单对象操作
Jan 17 Javascript
jQuery News Ticker 基于jQuery的即时新闻行情展示插件
Nov 05 Javascript
js下拉选择框与输入框联动实现添加选中值到输入框的方法
Aug 17 Javascript
Node.js环境下JavaScript实现单链表与双链表结构
Jun 12 Javascript
jQuery学习笔记——jqGrid的使用记录(实现分页、搜索功能)
Nov 09 Javascript
JavaScript之排序函数_动力节点Java学院整理
Jun 30 Javascript
html中通过JS获取JSON数据并加载的方法
Nov 30 Javascript
node.js博客项目开发手记
Mar 16 Javascript
Angular网络请求的封装方法
May 22 Javascript
js中getter和setter用法实例分析
Aug 14 Javascript
微信小程序实现锚点功能
Nov 20 Javascript
vue 中的 render 函数作用详解
Feb 28 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
关于js和php对url编码的处理方法
2014/03/04 PHP
Laravel使用Caching缓存数据减轻数据库查询压力的方法
2016/03/15 PHP
ASP SQL防注入的方法
2008/12/25 Javascript
jquery 多行滚动代码(附详细解释)
2010/06/17 Javascript
js中的hasOwnProperty和isPrototypeOf方法使用实例
2014/06/06 Javascript
Windows系统下Node.js的简单入门教程
2015/06/23 Javascript
jquery实现可横向和竖向展开的动态下滑菜单效果
2015/08/24 Javascript
javascript基础语法学习笔记
2016/01/04 Javascript
Js的Array数组对象详解
2016/02/22 Javascript
JS+HTML5实现的前端购物车功能插件实例【附demo源码下载】
2016/10/17 Javascript
React Form组件的实现封装杂谈
2018/05/07 Javascript
layui递归实现动态左侧菜单
2019/07/26 Javascript
vue中使用极验验证码的方法(附demo)
2019/12/04 Javascript
Vue data的数据响应式到底是如何实现的
2020/02/11 Javascript
python 判断一个进程是否存在
2009/04/09 Python
使用python编写android截屏脚本双击运行即可
2014/07/21 Python
浅谈MySQL中的触发器
2015/05/05 Python
python用10行代码实现对黄色图片的检测功能
2015/08/10 Python
Python文本统计功能之西游记用字统计操作示例
2018/05/07 Python
对numpy.append()里的axis的用法详解
2018/06/28 Python
pygame游戏之旅 计算游戏中躲过的障碍数量
2018/11/20 Python
使用IDLE的Python shell窗口实例详解
2019/11/19 Python
Python数据结构dict常用操作代码实例
2020/03/12 Python
django数据模型中null和blank的区别说明
2020/09/02 Python
HelloFresh澳大利亚:订购你的美味食品盒、健康餐食
2018/03/28 全球购物
Crocs波兰官方商店:女鞋、男鞋、童鞋、洞洞鞋
2019/10/08 全球购物
迪卡侬中国官网:Decathlon中国
2020/08/10 全球购物
remote接口和home接口主要作用
2013/05/15 面试题
接待员岗位责任制
2014/02/10 职场文书
爱国口号
2014/06/19 职场文书
网络妈妈观后感
2015/06/08 职场文书
校运会通讯稿
2015/07/18 职场文书
2015国庆节放假通知范文
2015/07/30 职场文书
母亲节感言
2015/08/03 职场文书
python 如何用map()函数创建多线程任务
2021/04/07 Python
Python实现灰色关联分析与结果可视化的详细代码
2022/03/25 Python