详解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 相关文章推荐
idTabs基于JQuery的根据URL参数选择Tab插件
Apr 11 Javascript
js中判断数字\字母\中文的正则表达式 (实例)
Jun 29 Javascript
jquery实现带单选按钮的表格行选中时高亮显示
Aug 01 Javascript
jquery数组封装使用方法分享(jquery数组遍历)
Mar 25 Javascript
jquery仅用6行代码实现滑动门效果
Sep 07 Javascript
JavaScript对象参数的引用传递
Jan 14 Javascript
bootstrap快速制作后台界面
Dec 05 Javascript
微信小程序 自己制作小组件实例详解
Dec 22 Javascript
jQuery扩展+xml实现表单验证功能的方法
Dec 25 Javascript
JS switch判断 三目运算 while 及 属性操作代码
Sep 03 Javascript
vue.js  父向子组件传参的实例代码
Oct 29 Javascript
node中短信api实现验证码登录的示例代码
Jan 20 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
无数据库的详细域名查询程序PHP版(4)
2006/10/09 PHP
怎么样可以把 phpinfo()屏蔽掉?
2006/11/24 PHP
关于PHP5 Session生命周期介绍
2010/03/02 PHP
php发送post请求函数分享
2014/03/06 PHP
PHP入门之常量简介和系统常量
2014/05/12 PHP
Yii实现多按钮保存与提交的方法
2014/12/03 PHP
详解php的socket通信
2015/08/11 PHP
jquery mobile实现拨打电话功能的几种方法
2013/08/05 Javascript
js实现二代身份证号码验证详解
2014/11/20 Javascript
jquery获取多个checkbox的值异步提交给php
2015/07/07 Javascript
Javascript验证方法大全
2015/09/21 Javascript
Node.js中使用socket创建私聊和公聊聊天室
2015/11/19 Javascript
整理Javascript函数学习笔记
2015/12/01 Javascript
JavaScript操作 url 中 search 部分方法函数
2016/06/15 Javascript
如何用js实现鼠标向上滚动时浮动导航
2016/07/18 Javascript
基于js中的原型、继承的一些想法
2016/08/10 Javascript
JavaScript数据结构中串的表示与应用实例
2017/04/12 Javascript
[01:04:02]DOTA2-DPC中国联赛 正赛 Elephant vs IG BO3 第二场 1月24日
2021/03/11 DOTA
Python中的Numeric包和Numarray包使用教程
2015/04/13 Python
Python实现读取TXT文件数据并存进内置数据库SQLite3的方法
2017/08/08 Python
linecache模块加载和缓存文件内容详解
2018/01/11 Python
numpy的文件存储.npy .npz 文件详解
2018/07/09 Python
Django用户登录与注册系统的实现示例
2020/06/03 Python
Python调用JavaScript代码的方法
2020/10/27 Python
python3实现名片管理系统(控制台版)
2020/11/29 Python
CSS3中Color的一些特性介绍
2012/05/27 HTML / CSS
CSS3之多背景background使用示例
2013/10/18 HTML / CSS
全球最大的在线旅游公司:Expedia
2017/11/16 全球购物
Bose法国官网:购买耳机、扬声器、家庭影院、专业音响
2017/12/21 全球购物
财产公证书
2014/04/10 职场文书
学用政策心得体会
2014/09/10 职场文书
校长师德师风自我剖析材料
2014/09/29 职场文书
电信营业员岗位职责
2015/04/14 职场文书
2015年文明创建工作总结
2015/04/30 职场文书
负责培养人意见
2015/06/05 职场文书
导游词之上海豫园
2019/10/24 职场文书