详解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中style属性
Oct 11 Javascript
js网页侧边随页面滚动广告效果实现
Apr 14 Javascript
JS跨域代码片段
Aug 30 Javascript
jquery点击展示与隐藏更多内容
Dec 03 Javascript
Javascript计算二维数组重复值示例代码
Dec 18 Javascript
js生成随机数方法和实例
Jan 17 Javascript
vue学习教程之带你一步步详细解析vue-cli
Dec 26 Javascript
在nginx上部署vue项目(history模式)的方法
Dec 28 Javascript
Node.js创建HTTP文件服务器的使用示例
May 11 Javascript
vue+element搭建后台小总结 el-dropdown下拉功能
Apr 10 Javascript
微信小程序实现注册登录功能(表单校验、错误提示)
Dec 10 Javascript
jQuery实现的解析本地 XML 文档操作示例
Apr 30 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获取文件后缀名的三个函数
2012/10/15 PHP
解析php框架codeigniter中如何使用框架的session
2013/06/24 PHP
Yii 快速,安全,专业的PHP框架
2014/09/03 PHP
php序列化函数serialize() 和 unserialize() 与原生函数对比
2015/05/08 PHP
Yii2.0 模态弹出框+ajax提交表单
2016/05/22 PHP
php将服务端的文件读出来显示在web页面实例
2016/10/31 PHP
PHP获取本周所有日期或者最近七天所有日期的方法
2018/06/20 PHP
laravel 获取当前url的别名方法
2019/10/11 PHP
js 剪切板应用clipboardData详细解析
2013/12/17 Javascript
jquery链式操作的正确使用方法
2014/01/06 Javascript
node.js中的emitter.emit方法使用说明
2014/12/10 Javascript
JavaScript代码生成PDF文件的方法
2016/02/26 Javascript
浏览器复制插件zeroclipboard使用指南
2016/03/26 Javascript
JS实用技巧小结(屏蔽错误、div滚动条设置、背景图片位置等)
2016/06/16 Javascript
深入浅出webpack教程系列_安装与基本打包用法和命令参数详解
2017/09/10 Javascript
一文让你彻底搞清楚javascript中的require、import与export
2017/09/24 Javascript
基于vue实现分页效果
2017/11/06 Javascript
基于vue-cli vue-router搭建底部导航栏移动前端项目
2018/02/28 Javascript
快速解决brew安装特定版本flow的问题
2018/05/17 Javascript
[53:29]完美世界DOTA2联赛循环赛 DM vs Matador BO2第二场 11.04
2020/11/05 DOTA
Python通过select实现异步IO的方法
2015/06/04 Python
详解Python的Django框架中的Cookie相关处理
2015/07/22 Python
python中map()函数的使用方法示例
2017/09/29 Python
Python编程pygame模块实现移动的小车示例代码
2018/01/03 Python
Python标准库shutil用法实例详解
2018/08/13 Python
Python解析多帧dicom数据详解
2020/01/13 Python
Flask和pyecharts实现动态数据可视化
2020/02/26 Python
使用darknet框架的imagenet数据分类预训练操作
2020/07/07 Python
墨西哥运动服饰和鞋网上商店:Netshoes墨西哥
2016/07/28 全球购物
美国在线家居装饰店:Belle&June
2018/10/24 全球购物
读完《骆驼祥子》的观后感!
2019/07/05 职场文书
JS新手入门数组处理的实用方法汇总
2021/04/07 Javascript
教你怎么用python selenium实现自动化测试
2021/05/27 Python
MySQL 分区表中分区键为什么必须是主键的一部分
2022/03/17 MySQL
Win11运行cmd提示“请求的操作需要提升”的两种解决方法
2022/07/07 数码科技
ssh服务器拒绝了密码 请再试一次已解决(亲测有效)
2022/08/14 Servers