详解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 面向对象编程(1) 基础
May 18 Javascript
PHP 与 js的通信(via ajax,json)
Nov 16 Javascript
jQuery实现统计输入文字个数的方法
Mar 11 Javascript
jQuery获得指定元素坐标的方法
Apr 14 Javascript
AngularJS 输入验证详解及实例代码
Jul 28 Javascript
AngularJS表格详解及示例代码
Aug 17 Javascript
JS图片放大效果简单实现代码
Sep 08 Javascript
jQuery实现菜单栏导航效果
Aug 15 jQuery
JS分页的实现(同步与异步)
Sep 16 Javascript
Three.js如何实现雾化效果示例代码
Sep 27 Javascript
webpack优化的深入理解
Dec 10 Javascript
原生js实现九宫格拖拽换位
Jan 26 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 从数据库提取二进制图片的处理代码
2009/09/09 PHP
改写ThinkPHP的U方法使其路由下分页正常
2014/07/02 PHP
PHP实现XML与数据格式进行转换类实例
2015/07/29 PHP
PHP模拟QQ登录的方法
2015/07/29 PHP
Zend Framework入门教程之Zend_Mail用法示例
2016/12/08 PHP
浅谈JavaScript中面向对象技术的模拟
2006/09/25 Javascript
彻底搞懂JS无缝滚动代码
2007/01/03 Javascript
js替代copy(示例代码)
2013/11/27 Javascript
使用javascript控制cookie显示和隐藏背景图
2014/02/12 Javascript
jQuery实现下拉加载功能实例代码
2016/04/01 Javascript
React创建组件的三种方式及其区别
2017/01/12 Javascript
JavaScript函数基础详解
2017/02/03 Javascript
简单实现jQuery上传图片显示预览功能
2020/06/29 jQuery
React-Router如何进行页面权限管理的方法
2017/12/06 Javascript
JS获取input[file]的值并显示在页面的实现方法
2018/03/09 Javascript
node实现登录图片验证码的示例代码
2018/04/20 Javascript
微信小程序实现文字无限轮播效果
2018/12/28 Javascript
mpvue微信小程序多列选择器用法之省份城市选择的实现
2019/03/07 Javascript
[00:55]2015国际邀请赛中国区预选赛5月23日——28日约战上海
2015/05/25 DOTA
Python中list列表的一些进阶使用方法介绍
2015/08/15 Python
深入理解Python中的*重复运算符
2017/10/28 Python
人脸识别经典算法一 特征脸方法(Eigenface)
2018/03/13 Python
Matplotlib中rcParams使用方法
2021/01/05 Python
朗仕(Lab series)英国官网:雅诗兰黛集团男士专属护肤品牌
2017/11/28 全球购物
TripAdvisor印尼站:全球领先的旅游网站
2018/03/15 全球购物
西班牙在线光学:Visual-Click
2020/06/22 全球购物
杭州-飞时达软件有限公司.net笔面试
2012/04/28 面试题
竞聘医务工作人员的自我评价分享
2013/11/04 职场文书
本科毕业生的求职信范文
2013/11/20 职场文书
供货协议书
2014/04/22 职场文书
《她是我的朋友》教学反思
2014/04/26 职场文书
2015年销售助理工作总结
2015/05/11 职场文书
六一儿童节主持开场白
2015/05/28 职场文书
2016优秀护士先进个人事迹材料
2016/02/25 职场文书
读《皮囊》有感:理解是对他人的最大的善举
2019/11/14 职场文书
使用css样式设计一个简单的html登陆界面的实现
2021/03/30 HTML / CSS