Vue 最独特的特性之一,是其非侵入性的响应式系统。数据模型仅仅是普通的 Javascript 对象。而当你修改它们时,视图会进行更新。这使得状态管理很是简单直接,不过理解其工做原理一样很是重要,这样你能够回避一些常见的问题。html
原理图剖析

仔细阅读这张官方原理图,大概能够剖析为如下几个步骤:vue
- 编译组件:对特殊标记的部分(好比双大括号部分)进行替换为相应的数据值。
- 收集依赖:对于编译阶段依赖的数据进行监听(这个都是经过
watcher
对象实现的)
- 通知更新:当步骤2中监听的数据发生变化时,会通知
watcher
进行从新计算,触发关联视图更新。
能够简单理解为一个发布订阅系统,固然这里的过程介绍比较通俗,单纯是为了理解而解释的,实际流程其实仍是很复杂的。若是想结合源码深刻了解的,建议去阅读这篇文章:
Vue 源码解析:深刻响应式原理react
关于Vuejs模板
上一篇中提到,Vue实例在初始化的时候会将目标节点 div#app
内容进行替换,是在定义了 template
属性或者 render
函数的前提下。不然会对把 div#app
内容当作模板进行编译输出。通常状况下,不多使用 template
属性来定义模板,由于实际开发过程当中,咱们的模板仍是很复杂的,单纯经过 template
属性定义,咱们的代码会显得很是臃肿,不便于阅读。因此,咱们一般使用的是将模板内容写在过载目标元素的内部,稍后将采用这种方法作代码演示。git
固然对于复杂项目,更多的是使用 单文件组件 (这将在之后专题文章中介绍)。github
百闻不如一见
还记得上一篇中讲述的如何实现一个简单的 Vue 应用,并经过不一样的 API 实现了目标元素的渲染吧。不记得也没关系,这里仍是从最基本的代码开始。下面咱们会动态的实现一个列表的渲染,并经过修改数据,来触发视图的更新,以此来感觉下响应式系统的快感。先来看一段代码:api
Demo1:data缓存
1 2 3 4 5 6 7 8
|
<div id="app"> <button v-on:click="addItem">添加</button> <ul> <li v-for="(item, index) in list" v-bind:key="index"> <a v-bind:href="item.url">{{ item.name }}</a> </li> </ul> </div>
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
|
var app = new Vue({ el: "#app", data () { return { count: 1, list: [ { name: 'Vuejs官网', url: 'https://cn.vuejs.org' }, { name: 'Github', url: 'https://github.com' }, { name: 'Yuga博客1', url: 'https://yugasun.com' } ] } }, methods: { addItem () { this.count++ this.list.push({ name: 'Yuga博客' + this.count, url: 'https://yugasun.com' }) } } })
|
上面 Vue 实例在初始化的时候,属性 data
中定义了一个站点列表,而后模板中经过 v-for
指令进行列表渲染,同时也使用了 v-bind 指令来进行属性绑定,而且经过 v-on
指令来监听按钮的 click
事件来执行 addItem
方法。当点击添加按钮,就会向 list
push一条数据,视图会再次更新。(关于指令相关文章将在下一篇中讲到,感兴趣的同窗能够先到官网学习了解。)数据结构
Demo2:computedapp
在实际开发过程当中,咱们的接口请求到的数据每每会差强人意,这时就须要咱们进行一些转化,来生成咱们想要的数据结构,固然最直接的方式就是每次请求完数据就经过固定函数处理一遍,可是这个得手动执行。此时 computed
计算属性就能够用上了。ide
咱们知道除了 data
中定义的数据能够再模板中使用外,computed
属性也能够。只不过 computed
中的属性是须要先进行计算,而后再返回想要的数据的。当咱们输出某个属性,必须依赖另一个 data
中的属性来动态计算得到的,此时使用 computed
就很是简单了。代码以下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
|
var app = new Vue({ el: "#app", data () { return { count: 1, // 实际开发中每每是经过接口请求获取 requestList: [ 'Vuejs官网-https://cn.vuejs.org', 'Github-https://github.com', 'Yuga博客1-https://yugasun.com' ] } }, computed: { list: function() { var list = []; this.requestList.map(function(item, index) { var tempArr = item.split('-'); list.push({ name: tempArr[0], url: tempArr[1] }); }) return list; } }, methods: { addItem () { this.count++ this.requestList.push('Yuga博客' + this.count + '-https://yugasun.com') } } })
|
关于计算属性 - computed
关于计算属性,其实有个细节是不少同窗没有注意到的,计算属性其实是能够修改的!那么如何才能修改呢?
其实计算属性不只能够定义为一个函数,也能够定义为一个含有 get/set
属性的对象。当咱们定义为一个函数是,Vue 内部会默认将这个函数赋值给 get
属性,通常 set
是未定义的。当咱们定义 set
属性后,就能够对它进行修改了。来看下面一段代码:
1 2 3 4
|
<div id="app"> <button v-on:click="changeName">改变姓名</button> <h2>{{ username }}</h2> </div>
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
|
var app = new Vue({ el: "#app", data () { return { firstName: 'Yuga', lastName: 'Sun' } }, computed: { username: { get: function() { return this.firstName + ' ' + this.lastName; }, set: function(newVal) { var names = newVal.split(' '); this.firstName = names[0]; this.lastName = names[1]; } } }, methods: { changeName () { if (this.username === 'Yuga Sun') { this.username = 'Summer Wu'; } else { this.username = 'Yuga Sun'; } } } })
|
当进行赋值操做 this.username = 'Summer Wu'
时,计算属性 username
的 set
函数就会被调用,同时也对 firstName
和 lastName
进行了相应的更新。这里看似是直接进行赋值操做,其实也是经过间接修改 firstName
和 lastName
来实现 username
的更新的。由于 计算属性是基于它依赖的值进行缓存的
,若是它依赖的值没有发生改变,它本身就无法发生改变。
关于侦听器 - watch
建立 Vue 应用时,咱们还提到过 watch
这个属性,它实际上是个对象,键是须要观察的表达式,值是对应的回调函数。值也能够是方法名,或者包含选项的对象。和上面的计算属性相似,他能够监听 值/表达式
的变化来执行回调函数。咱们先实现上面的功能:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
|
var app = new Vue({ el: "#app", data () { return { firstName: 'Yuga', lastName: 'Sun', username: 'Yuga Sun' } }, watch: { firstName: function(val, oldVal) { this.username = val + ' ' + this.lastName; }, lastName: function (val, oldVal) { this.username = this.firstName + ' ' + val; } }, methods: { changeName () { if (this.username === 'Yuga Sun') { this.firstName = 'Summer'; this.lastName = 'Wu'; } else { this.firstName = 'Yuga'; this.lastName = 'Sun'; } } } })
|
以上就是最基础用法,每每有些时候咱们的监听属性并无那么简单。每每是一个对象,这时当咱们修改该对象的属性时,如何实现监听呢?先看下面代码:
1 2 3 4
|
<div id="app"> <button v-on:click="changeName">改变姓名</button> <h4>{{ username }}</h4> </div>
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
|
var app = new Vue({ el: "#app", data () { return { userinfo: { firstName: 'Yuga', lastName: 'Sun', }, username: 'Yuga Sun' } }, watch: { userinfo: function (val, oldVal) { this.username = val.firstName + ' ' + val.lastName; } }, methods: { changeName () { if (this.username === 'Yuga Sun') { this.userinfo.firstName = 'Summer' this.userinfo.lastName = 'Wu' } else { this.userinfo.firstName = 'Yuga' this.userinfo.lastName = 'Sun' } } } })
|
此时不管咱们如何点击按钮,都没法改变 username
的值,由于 watch
侦听器默认只是侦听该对象自己的赋值操做,也就是直接对 this.userinfo
进行赋值操做时的变化,并未对其内部属性进行侦听。实际上对于侦听的值是能够为一个对象的,它还有个 deep
属性,用来设置是否侦听内部属性的变化,而回调函数是经过 handler
来设置的。咱们再次修改代码以下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
|
var app = new Vue({ el: "#app", data () { return { userinfo: { firstName: 'Yuga', lastName: 'Sun', }, username: 'Yuga Sun' } }, watch: { userinfo: { deep: true, handler: function (val, oldVal) { this.username = val.firstName + ' ' + val.lastName; } } }, methods: { changeName () { if (this.username === 'Yuga Sun') { this.userinfo.firstName = 'Summer' this.userinfo.lastName = 'Wu' } else { this.userinfo.firstName = 'Yuga' this.userinfo.lastName = 'Sun' } } } })
|
点击按钮,你会发现 username
能够根据按钮点击更新了,这个属性在实际项目中很是实用,由于每每咱们修改数据时,并非总体赋值,大部分时候都是局部修改属性的,因此这个时候就须要经过设置 deep
属性为 true
,来达到咱们的侦听目的。
问题来了,当侦听对象包含不少属性,而咱们只是须要监听其中的一个或某几个属性,这时若是咱们经过这种方式侦听全部内部属性的变化,天然就会形成内存的浪费。那么能不能只侦听单一内部属性的变化呢?答案是确定的。
其实在 watch
定义的时候,键是能够为须要观察的表达式
的,表达式
就是说明,咱们是能够写成对象属性访问表达式的。好比咱们只须要侦听姓氏的修改,咱们能够写成 userinfo.lastName
。再来看下面代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
|
var app = new Vue({ el: "#app", data () { return { userinfo: { firstName: 'Yuga', lastName: 'Sun', }, username: 'Yuga Sun' } }, watch: { 'userinfo.lastName': function (val, oldVal) { this.username = this.userinfo.firstName + ' ' + val; } }, methods: { changeName () { if (this.username === 'Yuga Sun') { this.userinfo.lastName = 'Wu' } else { this.userinfo.lastName = 'Sun' } } } })
|
这样咱们就能够根据项目实际状况,灵活的使用侦听器来侦听咱们所关注的属性了,赶忙动手试一试吧~
源码在此