详解VueJS 数据驱动和依赖追踪分析


Posted in Javascript onJuly 26, 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 ,具体可以查看另一篇随笔javascript 中 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();
    }
  }
})

这种添加依赖的方式实在太巧妙了 。

 这里我画了一个图来描述

详解VueJS 数据驱动和依赖追踪分析

最后通过一段代码简单测试一下

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 }

详解VueJS 数据驱动和依赖追踪分析

OK 大功告成

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持三水点靠木。

Javascript 相关文章推荐
prototype1.4中文手册
Sep 22 Javascript
收藏Javascript中常用的55个经典技巧
Aug 12 Javascript
基于Jquery与WebMethod投票功能实现代码
Jan 19 Javascript
jQuery页面加载初始化的3种方法(推荐)
Jun 02 Javascript
Vue.js展示AJAX数据简单示例讲解
Mar 29 Javascript
jQuery树插件zTree使用方法详解
May 02 jQuery
vue中使用sessionStorage记住密码功能
Jul 24 Javascript
JS实现的视频弹幕效果示例
Aug 17 Javascript
JavaScript事件发布/订阅模式原理与用法分析
Aug 21 Javascript
vue elementui el-form rules动态验证的实例代码详解
May 23 Javascript
使用 webpack 插件自动生成 vue 路由文件的方法
Aug 20 Javascript
vue实现户籍管理系统
May 29 Javascript
前端主流框架vue学习笔记第二篇
Jul 26 #Javascript
浅谈vue.js中v-for循环渲染
Jul 26 #Javascript
前端主流框架vue学习笔记第一篇
Jul 26 #Javascript
关于vue.js组件数据流的问题
Jul 26 #Javascript
Vue.js弹出模态框组件开发的示例代码
Jul 26 #Javascript
VueJs单页应用实现微信网页授权及微信分享功能示例
Jul 26 #Javascript
node实现简单的反向代理服务器
Jul 26 #Javascript
You might like
论建造顺序的重要性
2020/03/04 星际争霸
数据库查询记录php 多行多列显示
2009/08/15 PHP
php在程序中将网页生成word文档并提供下载的代码
2012/10/09 PHP
php启用sphinx全文搜索的实现方法
2014/12/24 PHP
PHP使用NuSOAP调用Web服务的方法
2015/07/18 PHP
javascript 子窗体父窗体相互传值方法
2010/05/31 Javascript
javascript基础知识大集锦(二) 推荐收藏
2011/01/13 Javascript
jquery trim() 功能源代码
2011/02/14 Javascript
ext前台接收action传过来的json数据示例
2014/06/17 Javascript
jQuery实用函数用法总结
2014/08/29 Javascript
jQuery使用之处理页面元素用法实例
2015/01/19 Javascript
Web安全测试之XSS实例讲解
2016/08/15 Javascript
JavaScript数组迭代方法
2017/03/03 Javascript
jQuery源码解读之extend()与工具方法、实例方法详解
2017/03/30 jQuery
Vue.js框架路由使用方法实例详解
2017/08/25 Javascript
jQuery轮播图实例详解
2018/08/15 jQuery
仿vue-cli搭建属于自己的脚手架的方法步骤
2019/04/17 Javascript
Vue + ts实现轮播插件的示例
2020/11/10 Javascript
js闭包和垃圾回收机制示例详解
2021/03/01 Javascript
python有证书的加密解密实现方法
2014/11/19 Python
python统计日志ip访问数的方法
2015/07/06 Python
通过实例浅析Python对比C语言的编程思想差异
2015/08/30 Python
python绘制铅球的运行轨迹代码分享
2017/11/14 Python
python实现猜数字小游戏
2020/03/24 Python
Python实用技巧之利用元组代替字典并为元组元素命名
2018/07/11 Python
python使用folium库绘制地图点击框
2018/09/21 Python
PyQt4 treewidget 选择改变颜色,并设置可编辑的方法
2019/06/17 Python
你还在@微信官方?聊聊Python生成你想要的微信头像
2019/09/25 Python
tensorflow dataset.shuffle、dataset.batch、dataset.repeat顺序区别详解
2020/06/03 Python
pytorch查看模型weight与grad方式
2020/06/24 Python
Python 爬虫性能相关总结
2020/08/03 Python
详解Python中第三方库Faker
2020/09/25 Python
文体活动实施方案
2014/03/27 职场文书
外语专业毕业生自荐信
2014/04/14 职场文书
放飞理想主题班会
2015/08/14 职场文书
nodejs利用readline提示输入内容实例代码
2021/07/15 NodeJs