Vue源代码笔记(一)数据绑定
VUE数据绑定介绍
数据绑定是vue的基础核心之一,本文以Vue对象(当然也包含VueComponent)里的data数据绑定为例,阐述整个绑定的过程。
Vue的数据绑定由三部分组成,
- Observer 监听Object里属性的变化,一旦有属性发生变化,会发布带有新值的通知。它是一个发布者的角色。
- Watcher 接收新值的通知,对新值进行处理,比如刷新控件值(如果控件值绑定data里的属性)。它是一个订阅者的角色。
- 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来用更新的值更新视图。
Vue源代码笔记(一)数据绑定的更多相关文章
- Vue:实践学习笔记(1)——快速使用
Vue:实践学习笔记(1)——快速使用 Vue基础知识 0.引入Vue 官方地址:Vue的官方下载地址 Vue推荐博客:keepfool 在你的程序中快速引入Vue: <!-- 开发环境版本,包 ...
- vue学习笔记(九)vue-cli中的组件通信
前言 在上一篇博客vue学习笔记(八)组件校验&通信中,我们学会了vue中组件的校验和父组件向子组件传递信息以及子组件通知父组件(父子组件通信),上一篇博客也提到那是对组件内容的刚刚开始,而本 ...
- Vue源码探究-数据绑定的实现
Vue源码探究-数据绑定的实现 本篇代码位于vue/src/core/observer/ 在总结完数据绑定实现的逻辑架构一篇后,已经对Vue的数据观察系统的角色和各自的功能有了比较透彻的了解,这一篇继 ...
- vue学习笔记(三)class和style绑定
前言 通过上一章的学习vue学习笔记(二)vue的生命周期和钩子函数,我们已经更近一步的知道了关于vue的一些知识,本篇博客将进一步探讨vue其它方面的内容,vue中关于class和style绑定,关 ...
- Vue学习笔记-Vue.js-2.X 学习(六)===>脚手架Vue-CLI(项目说明-Babel)
五 Vue学习-vue-cli脚手架学习(创建只选一个选项:Babel) 1. 项目目录说明 node_modules : 包管理文件夹 public : 静态资源 src : 源代码 gitign ...
- Vue学习笔记-vue-element-admin 前端学习
一 使用环境 开发系统: windows 后端IDE: PyCharm 前端IDE: VSCode 数据库: msyql,navicat 编程语言: python3.7 (Windows x86- ...
- Vue学习笔记-2
前言 本文非vue教程,仅为学习vue过程中的个人理解与笔记,有说的不正确的地方欢迎指正讨论 1.computed计算属性函数中不能使用vm变量 在计算属性的函数中,不能使用Vue构造函数返回的vm变 ...
- Vue学习笔记-1
前言 本文不是Vue.js的教程,只是一边看官网Vue的教程文档一边记录并总结学习过程中遇到的一些问题和思考的笔记. 1.vue和avalon一样,都不支持VM初始时不存在的属性 而在Angular里 ...
- Vue学习笔记-Vue基础入门
此篇文章是本人在学习Vue是做的部分笔记的一个整理,内容不是很全面,希望能对阅读文章的同学有点帮助. 什么是Vue? Vue.js (读音 /vjuː/,类似于 view) 是一套构建用户界面的渐进式 ...
随机推荐
- shell 的变量
一.自定义变量 1.字母或者下划线开头,由字母.数字.下划线组成,大小写敏感,在使用变量时,要在变量前加上前缀 $,一般变量由大写字母表示,并且英文开头,"=" 两边应没有空格.如 ...
- 安卓中经常使用控件遇到问题解决方法(持续更新和发现篇幅)(在textview上加一条线、待续)
TextView设置最多显示30个字符.超过部分显示...(省略号),有人说分别设置TextView的android:signature="true",而且设置android:el ...
- C++编写绚丽的界面
近期项目特别的操蛋,要用C++写出各种变态界面,今晚上赶工总算有了一点小的收货. 因为没有时间去写博文 ,等项目期完了 准备 写一系列 怎样在C++/win32/mfc开发高质量 可扩展界面组建 ...
- JAVA性能优化的五种方式
一,JAVA性能优化之设计优化 设计优化处于性能优化手段的上层.它往往须要在软件开发之前进行.在软件开发之前,系统架构师应该就评估系统可能存在的各种潜在问题和技术难点,并给出合理的设计方案,因为软件设 ...
- 使用Dagger2创建的第一个小样例
将Dagger系列的咖啡壶样例再做一下简化,作为Dagger2的入门的第一个小样例. 场景描写叙述:有一个电水壶,它使用一个加热器来烧水.电水壶具备的功能有:開始加热(on方法),结束加热(off方法 ...
- ThinkPHP5.0框架开发--第10章 TP5.0验证器
ThinkPHP5.0框架开发--第10章 TP5.0验证器 第10章 TP5.0验证器 ======================================= 今日学习 1.验证器 1) 控 ...
- Ubuntu新建用户并加入SUDO组
Ubuntu新建用户并加入SUDO组 新建用户: adduser xxxx 加入用户组: usermod -aG sudo username
- 整数转罗马数字 C++实现 java实现 leetcode系列(十二)
罗马数字包含以下七种字符: I, V, X, L,C,D 和 M. 字符 数值 I 1 V 5 X 10 L 50 C 100 D 500 M 1000 例如, 罗马数字 2 写做 II ,即为两个并 ...
- sicily 1342 开心的金明 (动规)
刷一下简单的背包问题 以下为代码: //1342. 开心的金明 #include <iostream> using namespace std; #define MAX(a,b) a> ...
- SparkSession - Spark SQL 的 入口
SparkSession - Spark SQL 的 入口 翻译自:https://jaceklaskowski.gitbooks.io/mastering-apache-spark/content/ ...