zoukankan      html  css  js  c++  java
  • Vue源代码笔记(一)数据绑定

    VUE数据绑定介绍

    数据绑定是vue的基础核心之一,本文以Vue对象(当然也包含VueComponent)里的data数据绑定为例,阐述整个绑定的过程。

    Vue的数据绑定由三部分组成,

    1. Observer 监听Object里属性的变化,一旦有属性发生变化,会发布带有新值的通知。它是一个发布者的角色。
    2. Watcher  接收新值的通知,对新值进行处理,比如刷新控件值(如果控件值绑定data里的属性)。它是一个订阅者的角色。
    3. Dep Observer和Water的粘合剂。 Observer的通知是发给Dep,然后Dep再转发给Watcher。 而Watcher作为订阅者,不是直接注册在Observer里,而是Dep里。Dep可接收多个Watcher,一定程度来说也是个Watcher管理器。

    以下是三者之间的关系图 

                                              

    以下是来自于Vue的本身自带的例子,由一个index.html和app.js组成。通过这个例子我们来看一下,整个数据绑定的过程。

    index.html

     1 <!DOCTYPE html>
     2 <html>
     3   <head>
     4     <meta charset="utf-8">
     5     <meta name="viewport" content="width=device-width,initial-scale=1.0">
     6     <title>test</title>
     7   </head>
     8   <body>
     9     <div id="app">
    10       {{message}}
    11       <button-counter></button-counter>
    12     </div>
    13     <!-- built files will be auto injected -->
    14 
    15     <script src="../../dist/vue.js"></script>
    16     <script src="app.js"></script>
    17   </body>
    18 </html>

    app.js

     1 Vue.component('button-counter', {
     2     data: function () {
     3       return {
     4         count: 0,
     5       }
     6     },
     7     methods:{
     8       onClick:function(){
     9         this.count++;
    10       },
    11     },
    12     template: '<div><button v-on:click="onClick">{{count}}</button></div>'
    13   })
    14 
    15 var app = new Vue({
    16     el: '#app',
    17     data: {
    18       message: 'Hello Vue!'
    19     }
    20   })

    运行代码后见下图,我们可以看见例子(index.html)中的<div  id=app>{{message}} ...</div>  message属性被监听和绑定订阅者。 Vue利用Object.defineProperty来监听对象属性值的变化和对变化的通知。

    对用于Object.defineProperty里定义的两个方法get和set,分别对应了Watcher对象订阅属性变化通知给Watcher对象

    Watcher对象的订阅过程

     其中,get: function reactiveGetter方法,用于Watcher订阅此属性变化通知。get方法会在该属性值触发读(read)动作的时候触发。以上图中的message为例,当发生类似代码 “let val = message"或者"message"时,就会触发get函数。 

    从上面的get: function reactiveGetter方法代码中,我们可以看到表示当Dep.target不为空时,就进行Watcher的订阅。Dep.target实际上指向的是一个Watcher对象。

    当调用类似下列代码时

    Dep.target = watcher; // Watcher对象

    this.messgae; 

    就会触发以下代码

     1  get: function reactiveGetter () {
     2       const value = getter ? getter.call(obj) : val
     3       if (Dep.target) { //Dep.taget指向一个Watcher对象
     4         dep.depend() //当Dep.target不为空时,调用Dep对象(dep)的depend()方法来进行watcher的订阅
     5         if (childOb) {
     6           childOb.dep.depend()
     7           if (Array.isArray(value)) {
     8             dependArray(value)
     9           }
    10         }
    11       }
    12       return value
    13     },

    //代码出自Vue源代码目录下的/src/core/instance/index.js

    我们再来看下depend做了什么

     1 export default class Dep {
     2   static target: ?Watcher;
     3   id: number;
     4   subs: Array<Watcher>;
     5 
     6   constructor () {
     7     this.id = uid++
     8     this.subs = []
     9   }
    10 
    11   addSub (sub: Watcher) { //将Watcher对象放入subs数组中,完成订阅动作,有属性变化后,Dep通知subs数组里所有的watcher对象。
    12     this.subs.push(sub)
    13   }
    14 
    15   removeSub (sub: Watcher) {
    16     remove(this.subs, sub)
    17   }
    18 
    19   depend () { //Watcher注入Dep对象
    20     if (Dep.target) {
    21       Dep.target.addDep(this)
    22     }
    23   }
    24 
    25   notify () {
    26     // stabilize the subscriber list first
    27     const subs = this.subs.slice()
    28     for (let i = 0, l = subs.length; i < l; i++) {
    29       subs[i].update()
    30     }
    31   }
    32 }
    33 
    34 // the current target watcher being evaluated.
    35 // this is globally unique because there could be only one
    36 // watcher being evaluated at any time.
    37 Dep.target = null
    38 const targetStack = []

    //代码出自Vue源代码目录下的/src/core/observer/dep.js

     从上面的Dep代码中可以看出,调用了Watcher对象(Dep.target)的addDep方法注入Dep对象,来订阅属性变化通知。 接下来,我们再看一下addDep做了些什么。

     1 export default class Watcher {
     2   
     3    ............
     4 
     5   /**
     6    * Add a dependency to this directive.
     7    */
     8   addDep (dep: Dep) {
     9     const id = dep.id
    10     if (!this.newDepIds.has(id)) {
    11       this.newDepIds.add(id)
    12       this.newDeps.push(dep)
    13       if (!this.depIds.has(id)) {
    14         dep.addSub(this)
    15       }
    16     }
    17   }
    18 
    19  .............
    20   
    21 }
    22 
    23
    //代码出自Vue源代码目录下的/src/core/observer/watcher.js

     我们可以从addDep方法里看到,最终调用了Dep的addSub方法。结合上面已列出的Dep代码。最终将watcher对象放入了Dep对象的subs数组中,完成了订阅动作。

    Watcher对象接收属性值变化通知

    set: reactiveSetter方法,在属性值执行写操作时(就是被赋值),会被触发。意味着类似代码"this.message='hello again' "就会触发set函数,执行下列代码

     1 set: function reactiveSetter (newVal) {
     2       const value = getter ? getter.call(obj) : val
     3       /* eslint-disable no-self-compare */
     4       if (newVal === value || (newVal !== newVal && value !== value)) {
     5         return
     6       }
     7       /* eslint-enable no-self-compare */
     8       if (process.env.NODE_ENV !== 'production' && customSetter)     
     9       {
    10         customSetter()
    11       }
    12       if (setter) {
    13         setter.call(obj, newVal)
    14       } else {
    15         val = newVal
    16       }
    17       childOb = !shallow && observe(newVal)
    18       dep.notify()//通知watcher对象,value值发生变化。
    19     }   

    //代码出自Vue源代码目录下的/src/core/instance/index.js

    在set函数中,调用dep.notify将最新的属性值通知dep对象里的所有watcher(保存在subs数组中),watcher对象调用update方法更新视图(参照上面提供的Dep代码)。

    总结

    1.Vue数据绑定,由Observer,Dep和Watcher组成。 Observer监测属性变化,发送变化通知。 Watcher订阅变化通知,根据通知里的最新属性值,更新视图。 Dep链接Observer和Watcher,转发Observer的通知到Watcher,Watcher通过Dep订阅Observer的通知。

    2.Observer利用Object.defineProperty定义的get方法,监控属性的读操作。在读操作中调用get方法,如果Dep.target指向了一个Watcher对象,就调用Dep.depend-->Watcher.addDep-->Dep.addSub订阅属性变化通知。

    3.Observer利用Object.defineProperty定义的set方法,监控属性的写操作(即更新属性值),在写操作中调用set方法,set方法里调用Dep.notify-->Watcher.update来用更新的值更新视图。

  • 相关阅读:
    【linux】驱动-5-驱动框架分层分离&实战
    【linux】驱动-4-LED芯片手册分析
    【MCU】国民N32固件库移植
    【MCU】移植AT32库&FreeRTOS教程
    P3768 简单的数学题
    P4301 [CQOI2013] 新Nim游戏
    P4767 [IOI2000]邮局
    P3211 [HNOI2011]XOR和路径
    FWT 笔记
    P3175 [HAOI2015]按位或(max-min 容斥)
  • 原文地址:https://www.cnblogs.com/andreitang/p/10158494.html
Copyright © 2011-2022 走看看