详解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 相关文章推荐
Dom在ajax技术中的作用说明
Oct 25 Javascript
js判断变量是否未定义的代码
Mar 28 Javascript
原生javascript实现拖动元素示例代码
Sep 01 Javascript
详解页面滚动值scrollTop在FireFox与Chrome浏览器间的兼容问题
Dec 03 Javascript
jquery特效 点击展示与隐藏全文
Dec 09 Javascript
jQuery使用serialize()表单序列化时出现中文乱码问题的解决办法
Jul 27 Javascript
基于Vue实现图书管理功能
Oct 17 Javascript
基于Vue的ajax公共方法(详解)
Jan 20 Javascript
node使用promise替代回调函数
May 07 Javascript
2种在vue项目中使用百度地图的简单方法
Sep 28 Javascript
django简单的前后端分离的数据传输实例 axios
May 18 Javascript
详解如何在Canvas中添加事件的方法
Apr 17 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 生成随机验证码图片代码
2010/02/08 PHP
解析thinkphp的左右值无限分类
2013/06/20 PHP
深入解析php中的foreach问题
2013/06/30 PHP
学习php中的正则表达式
2014/08/17 PHP
php微信高级接口调用方法(自定义菜单接口、客服接口、二维码)
2016/11/28 PHP
PHP中的自动加载操作实现方法详解
2019/08/06 PHP
js chrome浏览器判断代码
2010/03/28 Javascript
js获取当前月的第一天和最后一天的小例子
2013/11/18 Javascript
seaJs的模块定义和模块加载浅析
2014/06/06 Javascript
浅谈Javascript数组的使用
2015/07/29 Javascript
JS动态创建元素的两种方法
2016/04/20 Javascript
BootStrap 超链接变按钮的实现方法
2016/09/25 Javascript
jQuery 选择符详细介绍及整理
2016/12/02 Javascript
详解AngularJS ui-sref的简单使用
2017/04/24 Javascript
微信小程序支付之c#后台实现方法
2017/10/19 Javascript
node打造微信个人号机器人的方法示例
2018/04/26 Javascript
zepto.js 实时监听输入框的方法
2018/12/04 Javascript
vue elementUI使用tabs与导航栏联动
2019/06/21 Javascript
bootstrap-table+treegrid实现树形表格
2019/07/26 Javascript
Flutter实现仿微信底部菜单栏功能
2019/09/18 Javascript
微信小程序实现导航栏和内容上下联动功能代码
2020/06/29 Javascript
vue操作dom元素的3种方法示例
2020/09/20 Javascript
Python语言编写电脑时间自动同步小工具
2013/03/08 Python
pandas object格式转float64格式的方法
2018/04/10 Python
Django框架模板注入操作示例【变量传递到模板】
2018/12/19 Python
如何解决pycharm调试报错的问题
2020/08/06 Python
Numpy中的数组搜索中np.where方法详细介绍
2021/01/08 Python
python 第三方库paramiko的常用方式
2021/02/20 Python
请问如下代码执行后a和b的值分别是什么
2016/05/05 面试题
护士个人简历自荐信
2013/10/18 职场文书
语文教学感言
2014/02/06 职场文书
人民调解协议书范本
2014/10/11 职场文书
2015年艾滋病防治工作总结
2015/05/22 职场文书
2015迎新晚会活动总结
2015/07/16 职场文书
vue如何实现关闭对话框后刷新列表
2022/04/08 Vue.js
python如何查找列表中元素的位置
2022/05/30 Python