详解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 相关文章推荐
window.open被浏览器拦截后的自定义提示效果代码
Nov 19 Javascript
javascript在当前窗口关闭前检测窗口是否关闭
Sep 29 Javascript
node.js中的fs.readdir方法使用说明
Dec 17 Javascript
jquery搜索框效果实现方法
Jan 16 Javascript
jQuery Easyui学习之datagrid 动态添加、移除editor
Jan 27 Javascript
javascript冒泡排序小结
Apr 10 Javascript
使用bootstrap validator的remote验证代码经验分享(推荐)
Sep 21 Javascript
Angular2+国际化方案(ngx-translate)的示例代码
Aug 23 Javascript
Vue响应式原理深入解析及注意事项
Dec 11 Javascript
vue2.0实现列表数据增加和删除
Jun 17 Javascript
解决js中的setInterval清空定时器不管用问题
Nov 17 Javascript
动态规划之使用备忘录来改进Javascript函数
Apr 07 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
极典R601SW收音机
2021/03/02 无线电
基于mysql的论坛(7)
2006/10/09 PHP
深入浅析PHP7.0新特征(五大新特征)
2015/10/29 PHP
PHP常见的6个错误提示及解决方法
2016/07/07 PHP
php+mysql+jquery实现简易的检索自动补全提示功能
2017/04/15 PHP
利用php + Laravel如何实现部署自动化详解
2017/10/11 PHP
php swoole多进程/多线程用法示例【基于php7nts版】
2019/08/12 PHP
解决在laravel中leftjoin带条件查询没有返回右表为NULL的问题
2019/10/15 PHP
Yii框架学习笔记之应用组件操作示例
2019/11/13 PHP
JavaScript脚本语言在网页中的简单应用
2007/05/13 Javascript
jqTransform form表单美化插件使用方法
2012/07/05 Javascript
详解jQuery插件开发中的extend方法
2013/11/19 Javascript
JavaScript实现点击自动选择TextArea文本的方法
2015/07/02 Javascript
浅谈JavaScript 的执行顺序
2015/08/07 Javascript
Vue 与 Vuex 的第一次接触遇到的坑
2018/08/16 Javascript
JavaScript中常用的简洁高级技巧总结
2019/03/10 Javascript
Nuxt.js实战和配置详解
2019/08/05 Javascript
JQuery常用简单动画操作方法回顾与总结
2019/12/07 jQuery
nodejs实现的http、https 请求封装操作示例
2020/02/06 NodeJs
JS实现纸牌发牌动画
2021/01/19 Javascript
python对DICOM图像的读取方法详解
2017/07/17 Python
Python基于tkinter模块实现的改名小工具示例
2017/07/27 Python
Python中join函数简单代码示例
2018/01/09 Python
基于python requests库中的代理实例讲解
2018/05/07 Python
python模块导入的方法
2019/10/24 Python
python验证码图片处理(二值化)
2019/11/01 Python
Python tkinter布局与按钮间距设置方式
2020/03/04 Python
python时间time模块处理大全
2020/10/25 Python
HTML4和HTML5之间除了相似以外的10个主要不同
2012/12/13 HTML / CSS
您附近的水疗和健康场所:Spafinder(美国)
2019/07/05 全球购物
临床医学大学生求职信
2013/09/28 职场文书
新闻专业学生的自我评价
2014/02/13 职场文书
社会工作专业求职信
2014/07/15 职场文书
安全生产会议制度
2015/08/06 职场文书
导游词之沈阳植物园
2019/11/30 职场文书
go设置多个GOPATH的方式
2021/05/05 Golang