资讯专栏INFORMATION COLUMN

Vue源码解析(二)-MVVM双向绑定&&Watcher介绍

miya / 515人阅读

摘要:前言上一遍文章介绍了模版渲染的实现,这篇文章将继续介绍双向绑定的实现官网如下,当。

前言

上一遍文章介绍了Vue模版渲染的实现(https://segmentfault.com/a/11...),这篇文章将继续介绍双向绑定的实现

demo

官网demo如下,当data。message的值变化,input的value值也会相应的变化;当用户改变input框中的内容时data.message的值也会跟着改变

new Vue({ el: "#app", template: `

Message is: {{ message }}

`, data(){ return { message: "jixiangwu", } } })
ViewModel变化 -> View更新

当数据变化时,视图会直接更新,在本例中当data.message改变时,dom中绑定了data.message的视图都会更新
上一篇文章中介绍过,new Vue的过程中会将template字符串转换成render函数,render函数执行后会得到vnode对象(虚拟dom),在调用_update方法会将虚拟dom更新为真实的浏览器dom,代码如下:

    updateComponent = function () {
    //vm._render()生成vnode对象,vm._update()更新dom
      vm._update(vm._render(), hydrating);
    };
    //对vue实例新建一个Watcher监听对象,每当vm.data数据有变化,Watcher监听到后负责调用updateComponent进行dom更新
    vm._watcher = new Watcher(vm, updateComponent, noop);

updateComponent方法在Watcher初始化时会调用一次,后续的调用就涉及到MVVM的机制了,让我们从头开始分析
Vue初始化时会对data中的所有属性进行observe,调用defineReactive方法,将data属性转化为getter/setters存取方式。本文demo中的data={message:“jixiangwu”}相当于如下的调用:defineReactive(vm.data,"message",vm.data["message"])

//vue对象的生命周期中会调用initData方法
function initData (vm) {
   var data = vm.$options.data;
   observe(data, true /* asRootData */);
}
function observe (value, asRootData) {
    ob = new Observer(value);
}
//对data进行监听
var Observer = function Observer (value) {
  if (Array.isArray(value)) {
    this.observeArray(value);
  } else {
    this.walk(value);
  }
}
//对data中的所有属性调用defineReactive,将其转化为getter/setters存取方式
//Walk through each property and convert them into getter/setters.
Observer.prototype.walk = function walk (obj) {
  var keys = Object.keys(obj);
  for (var i = 0; i < keys.length; i++) {
      defineReactive(obj, keys[i], obj[keys[i]]);
  }
};
function defineReactive(obj,key,val){
  //利用闭包为每个属性绑定一个dep对象(可视为发布者,负责发布属性是否有变化)
  const dep = new Dep();
  Object.defineProperty(obj, key, {
    enumerable: true,
    configurable: true,
    get: function reactiveGetter () {
      //每次new一个watcher(订阅者)对象的时候需要计算依赖的dep对象,Dep.target就是当前正在计算依赖的watcher对象
      if (Dep.target) {
      //调用属性的getter方法时,存在Dep.target则将当前dep和watcher绑定
        dep.depend();
      }
    },
    set: function reactiveSetter (newVal) {
      //调用属性的setter方法时,dep同时发布一次属性变化的通知到所有依赖的watcher对象
      dep.notify();
    }
  }
}

defineReactive用到了Object.defineProperty 方法,这也是vue不支持ie8的原因,这个方法的主要作用就是set和get函数,同时也可以看到vue针对data中的所有属性都会new一个dep对象,dep对象里面会存放所有依赖此属性的watcher对象,此处用到了发布/订阅模式,dep和watcher分别是发布者和订阅者,每当data中的属性变化dep对象就会通知所有依赖的watcher去更新dom,下面详细分析一下这个过程
上一篇提到,由于template中引用了{{ message }}属性,因此render函数里面会调用到vm.meessage,这时就会触发defineReactive设置的get方法,get方法里面就会进行(该属性)依赖的收集,那么get方法里的Dep.target是啥呢?
上一篇提到dom初次渲染是通过(监听整个模版的)watcher对象初始化时调用watcher.get方法实现的,watcher.get方法主要是计算getter函数的值(本例中是updateComponent,更新dom)和计算依赖(哪些属性的dep对象),Dep.target就是当前接受计算(依赖)的全局惟一的watcher对象,具体方法如下:
1、pushTarget(this),将this(当前watcher对象)赋值给Dep.target
2、调用this.getter,this.getter会访问所有依赖的属性,同时触发属性的getter方法
3、调用属性getter方法中的dep.depend(),完成dep和wathcher的绑定
4、popTarget()将Dep.target值设为targetStack栈中的上一个(没有则为空)

// the current target watcher being evaluated.
// this is globally unique because there could be only one
// watcher being evaluated at any time.
// 英文注释都是源码作者的注释
Dep.target = null;
var targetStack = [];
//Evaluate the getter, and re-collect dependencies.
Watcher.prototype.get = function get () {
  //将this赋值给Dep.target
  pushTarget(this);  
  //执行wacther的更新操作,本文中是执行updateComponent方法
  this.getter.call(vm);
  popTarget();
}
function pushTarget (_target) {
  if (Dep.target) { targetStack.push(Dep.target); }
  Dep.target = _target;
}
function popTarget () {
  Dep.target = targetStack.pop();
}

继续看defineReactive中dep.depend方法干了啥,其实就是dep对象上维护了一个watcher对象的队列,wathcer对象上也维护了一份dep的队列

Dep.prototype.depend = function depend () {
  if (Dep.target) {
    Dep.target.addDep(this);
  }
};
Watcher.prototype.addDep = function addDep (dep) {
  var id = dep.id;
  //将dep对象加入到wather对象的newDeps队列中
  this.newDepIds.add(id);
  this.newDeps.push(dep);
  if (!this.depIds.has(id)) {
    // 同时将watcher对象也加入到dep对象的subs队列中
    dep.addSub(this);
  }
};
Dep.prototype.addSub = function addSub (sub) {
  this.subs.push(sub);
};

data值变化时会触发setter方法中的dep.notify,通知绑定在dep对象上的所有watcher对象调用update方法更新视图(watcher.update最终调用了updateComponent,用到了缓存队列,不一定立即触发)

Dep.prototype.notify = function notify () {
  // stabilize the subscriber list first
  var subs = this.subs.slice();
  for (var i = 0, l = subs.length; i < l; i++) {
    subs[i].update();
  }
};

总结
1、对data进行observe,针对data属性调用Object.defineProperty设置getter和setter,同时绑定一个dep对象
2、new Watcher(vm, updateComponent, noop)监听整个dom的变化
3、watcher初始化时调用updateComponent,updateComponent调用render函数更新dom(此时还会将该watcher对象赋值给全局对象Dep.target,进行依赖收集)
4、在watcher对象依赖收集期间,render函数访问data中的属性(如本例的data.message),触发data.message的getter方法,在getter方法中会将data.message绑定的dep对象和wathcer对象建立对应关系(互相加入到对方维护的队列属性上)
5、后续data属性的值变化时dep对象会通知所有依赖此data属性的watcher对象调用updateComponent方法更新视图

View变化 -> ViewModel更新

视图变化 -> 数据更新主要是通过v-model实现的,v-model本质上不过是语法糖,它负责监听用户的输入事件以更新数据,本例中

基本等同于下面的效果

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/89511.html

相关文章

  • Vue源码解析(三)-computed计算属性&amp;&amp;lazy watcher

    摘要:前言源码解析一模版渲染源码解析二双向绑定官网给出的如下结果源码分析判断参数是否包含属性本例中本例中和是函数监听计算属性设置,延迟执行的方法设置可以通过本例方式访问计算属性对象初始化时会针对属性的所有值分别一个对象,在源码解析二中有详细介 前言 1、Vue源码解析(一)-模版渲染2、Vue源码解析(二)-MVVM双向绑定 demo 官网给出的demo如下 new Vue({ el...

    CoderStudy 评论0 收藏0
  • 剖析Vue原理&amp;实现双向绑定MVVM

    摘要:所以无需太过介怀是实现的单向或双向绑定。监听数据绑定更新函数的处理是在这个方法中,通过添加回调来接收数据变化的通知至此,一个简单的就完成了,完整代码。 本文能帮你做什么?1、了解vue的双向数据绑定原理以及核心代码模块2、缓解好奇心的同时了解如何实现双向绑定为了便于说明原理与实现,本文相关代码主要摘自vue源码, 并进行了简化改造,相对较简陋,并未考虑到数组的处理、数据的循环依赖等,也...

    melody_lql 评论0 收藏0
  • VUE - MVVM - part13 - inject &amp; 总结

    摘要:通过装作这些变化,我们实现了从而到达了数据变化触发函数的过程。于此同时,我们还实现了来扩展这个可响应的结构,让这个对象拥有了触发和响应事件的能力。最后,根据我们的实现,这是最终的产出,一个框架,了解一下系列文章地址优化优化总结 看这篇之前,如果没有看过之前的文章,移步拉到文章末尾查看之前的文章。 provide / inject 在上一步我们实现了,父子组件,和 props 一样 pr...

    niuxiaowei111 评论0 收藏0
  • VueJS源码学习——项目结构&amp;目录

    摘要:所以整个的核心,就是如何实现这三样东西以上摘自囧克斯博客的一篇文章从版本开始这个时候的项目结构如下源码在里面,为打包编译的代码,为打包后代码放置的位置,为测试代码目录。节点类型摘自资源另一位作者关于源码解析 本项目的源码学习笔记是基于 Vue 1.0.9 版本的也就是最早的 tag 版本,之所以选择这个版本,是因为这个是最原始没有太多功能拓展的版本,有利于更好的看到 Vue 最开始的骨...

    ad6623 评论0 收藏0
  • 自己实现MVVMVue源码解析

    摘要:无论是双向绑定还是单向绑定,都是符合思想的。看了的源码后不难发现的双向绑定的实现也就是在表单元素上添加了事件,可以说双向绑定是单向绑定的一个语法糖。 前言 本文会带大家手动实现一个双向绑定过程(仅仅涵盖一些简单的指令解析,如:v-text,v-model,插值),当然借鉴的是Vue1的源码,相信大家在阅读完本文后对Vue1会有一个更好的理解,源代码放到了github,由于本人水平有限,...

    ?xiaoxiao, 评论0 收藏0

发表评论

0条评论

miya

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<