此学习教程是对官方教程的解析,
本章节主要涉及到的官方教程地址:
Vue实例—— Vue.js,计算属性和侦听器——Vue.js
四. 数据
1. 数据的特性:响应式
1.1 响应式属性
当一个 Vue 实例被创建时,它将 data
对象中的所有的属性加入到 Vue 的响应式系统中。当这些属性的值发生改变时,视图将会产生“响应”,即匹配更新为新的值。
当这些数据改变时,视图会进行重渲染。值得注意的是只有当实例被创建时就已经存在于 data
中的属性才是响应式的。
理解:
根据官方教程,响应式属性可以定义如下:
响应式属性: Vue实例里面的data
对象中的属性(当实例被创建时就已经存在) 的值发生改变时, 视图将会产生“响应”,即匹配更新为新的值。
根据上面的定义,响应式属性必须是Vue实例里面的data
对象中的属性。但是真的是这样吗?我们看下如下例子:
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
</head>
<body>
<div id="app">
</div>
<script>
var Comp = Vue.extend({
props: ['msg'],
template: '<div>comp.msg:{{ msg }}<Br/>comp.msg2:{{ msg2 }}</div>'
})
var comp = new Comp({
el: "#app",
propsData: {
msg: 'hello'
},
data:{
msg2: 'hello2'
}
})
</script>
</body>
</html>
这个页面访问后,可以发现对comp.msg2更改,视图也会响应,说明外部传入属性propsData也是响应式属性。而这个局部注册的Comp组件是Vue的子类,本质上也是Vue实例。
所以我们可以重新定义下:
响应式属性: Vue实例里面的data/
propsData对象中的属性(当实例被创建时就已经存在) 的值发生改变时, 视图将会产生“响应”,即匹配更新为新的值。
(1) 必须是Vue实例里面的data/
propsData对象中的属性
(2) 前提是当实例被创建时就已经存在
(3)“响应", 指的是data的属性一但改变了,就会发出通知给响应式系统,系统再反馈给视图,视图得到通知反映这种改变。所以,响应主要是指不需要人为干预的、自动化地处理了这种改变,一般是反映速度很快、即时的,但并不是立即、不需要时间的。
1.2 非响应式属性
1.2.1 当实例被创建时就不存在,而在实例创建后赋值的属性
当实例被创建时就已经存在是响应式属性的前提,所以如果这个前提条件不符合,当然就不是响应式属性了。
直接看简单的例子:
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
</head>
<body>
<div id="app">
<p>a_value: {{ a }}</p>
<p>b_value: {{ b }}</p>
</div>
<script>
var vm = new Vue({
el:"#app",
data: { a: 1 }
})
vm.b = '2'
</script>
</body>
</html>
运行结果:
可以看到,实例化后赋值的b属性对模板没有任何效果。
1.2.2 使用 Object.freeze()来包装的data对象的属性
使用Object.freeze()
会阻止修改现有的 property,也意味着响应系统无法再追踪变化。
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
</head>
<body>
<div id="app">
<p>a_value: {{ a }}</p>
</div>
<script>
var vm = new Vue({
el:"#app",
data: Object.freeze({ a: 1 })
})
</script>
</body>
</html>
运行结果:
2.计算属性
看一下官方教程的例子:
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
</head>
<body>
<div id="example">
<p>Original message: {{ message }}</p>
<p>Computed reversed message: "{{ reversedMessage }}"</p>
</div>
<script>
var vm = new Vue({
el: '#example',
data: {message: 'Hello'},
//data: Object.freeze({foo: 'bar'})
computed: {
// 计算属性
reversedMessage: function () {
return this.message.split('').reverse().join('')
}
}
})
</script>
</body>
</html>
官方教程:
这里我们声明了一个计算属性 reversedMessage
。我们提供的函数将用作 property vm.reversedMessage
的 getter 函数。
你可以打开浏览器的控制台,自行修改例子中的 vm。vm.reversedMessage
的值始终取决于 vm.message
的值。
你可以像绑定普通 property 一样在模板中绑定计算属性。Vue 知道 vm.reversedMessage
依赖于 vm.message
,因此当 vm.message
发生改变时,所有依赖 vm.reversedMessage
的绑定也会更新。而且最妙的是我们已经以声明的方式创建了这种依赖关系:计算属性的 getter 函数是没有副作用 (side effect) 的,这使它更易于测试和理解。
我们可以将同一函数定义为一个方法而不是一个计算属性。两种方式的最终结果确实是完全相同的。然而,不同的是计算属性是基于它们的响应式依赖进行缓存的。只在相关响应式依赖发生改变时它们才会重新求值。这就意味着只要 message
还没有发生改变,多次访问 reversedMessage
计算属性会立即返回之前的计算结果,而不必再次执行函数。
理解:
计算属性:依赖响应式属性,并对响应式属性进行计算并对计算结果进行缓存(除非依赖的响应式属性变化才重新计算)的声明式绑定模板的属性
(1) 声明式绑定模板:计算属性与响应式属性一样,都是Vue应用的属性,可与模板进行声明式绑定
(2) 依赖响应式属性, 对响应式属性进行计算:计算属性不能对自身直接赋值, 它的值是响应式属性的计算值
(4) 对计算结果进行缓存:响应式属性的值随依赖的响应式属性变化而变化,所以依赖的响应式属性不变的话,它也不变,所以没必要重新计算了,只要响应式属性不变,就把计算值缓存起来。所以计算属性可以放一些复杂的计算而不对性能造成很大影响。
计算属性默认是一个getter函数,所以上面的例子等同于:
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
</head>
<body>
<div id="example">
<p>Original message: {{ message }}</p>
<p>Computed reversed message: "{{ reversedMessage }}"</p>
</div>
<script>
var vm = new Vue({
el: '#example',
data: {message: 'Hello'},
//data: Object.freeze({foo: 'bar'})
computed: {
reversedMessage: {
// 计算属性的 getter
get:function () {
// `this` 指向 vm 实例
return this.message.split('').reverse().join('')
},
// 虽然你可以设置setter,但不建议这样做, 因为对自身的直接赋值将会抛出错误
// 虽然你可做其它逻辑,但显然会让逻辑混乱。
/*set: function (newValue) {
this.reversedMessage = newValue;//会抛出错误
}*/
}
}
})
</script>
</body>
</html>
计算属性也可以用方法/过滤器来实现,它们的区别在于计算属性有缓存而方法/过滤器没有缓存。
看下面的例子来看下同样效果的不同实现方式:
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
</head>
<body>
<div id="example">
<p>Original message: {{ message }}</p>
<p>reversed message: "{{ reversedMessage }}"</p>
<p>reversed message: "{{ reverseMessage(message) }}"</p>
<p>reversed message: "{{ message | reverseMessageFilter }}"</p>
</div>
<script>
var vm = new Vue({
el: '#example',
data: {message: 'Hello'},
//data: Object.freeze({foo: 'bar'})
computed: {
reversedMessage: function () {
return this.message.split('').reverse().join('')
}
},
methods: {
reverseMessage: function (message) {
return message.split('').reverse().join('')
}
},
filters: {
reverseMessageFilter: function (message) {
return message.split('').reverse().join('')
}
}
})
</script>
</body>
</html>
另一个实现同样的效果使用侦听器的例子:
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
</head>
<body>
<div id="example">
<p>Original message: {{ message }}</p>
<p>reversed message: "{{ reversedMessage }}"</p>
</div>
<script>
var vm = new Vue({
el: '#example',
data: {
message: 'Hello',
reversedMessage: 'olleH'
},
watch: {
message: function (val) {
this.reversedMessage = this.message.split('').reverse().join('')
}
}
})
</script>
</body>
</html>
可以看到侦听器不创建新的属性,它只是对现有响应式属性进行侦听,如响应式属性发生变化,再实现相应的逻辑。
那什么时候用侦听器而不使用计算属性?当需要在数据变化时执行异步或开销较大的操作时。这个可以参考官网的例子,就不赘述了。
本章节教程结束。
全部教程地址:Vue入门实战教程 | 寒于水学习网