深入响应式原理
如何追踪变化
当Vue初始化时,会遍历data对象中的所有属性,并将这些属性通过Object.defineProperty 转换为setter和getter。
vue的每一个组件实例都有一个watcher对象,当组件渲染时,其属性将被标记为依赖,一旦依赖项的setter被调用,就会通知watcher重新计算,从而与其相关的组件将会更新。
变化检测问题
受到javascript的限制,vue无法检测到对象属性的添加或者删除,因此,setter和getter的转化只会在vue实例初始化时实现一次。在这之后新增的任何属性,vue都无法动态检测到该属性的变化,如:
<body>
<div id='vueDemo'><div v-if='showDiv'>{{message}}</div>
<button @click='show'></button>
</div>
</body>
<script type="text/javascript">
var vueTest=new Vue({
el:'#vueDemo',
data:{
showDiv:false,
},
methods:{
show:function(){
this.message='djiowu';
this.showDiv=!this.showDiv;
}
}
})
</script>
以上代码,由于message是在初始化之后添加的,故当对vueTest.message重新赋值为‘ddddd’,页面上的显示仍然是djiowu,无法随着属性值的变化动态更新页面。
而如果在初始化之前添加,即data:{showDiv:false,message:'djiowu'},则将vueTest.message重新赋值后,页面会随之而更新。
虽然vue无法在初始化后添加根级响应属性,但却可以通过Vue.set方法来给转换为setter/getter的现有根基响应属性添加子属性。注意,通过vueTest.attr.innerAttr='ddddd'这种直接赋值方法不生效,必须通过Vue.set方法添加才可以。如下例,虽然是在初始化之后添加的属性,vue却仍然可以随着属性值的变化动态更新页面
<div id='vueDemo'><div v-if='showDiv'>{{message.innerAttr}}</div>
<button @click='show'></button>
</div>
</body>
<script type="text/javascript">
var vueTest=new Vue({
el:'#vueDemo',
data:{
showDiv:false,
message:{}
},
methods:{
show:function(){
this.$set(this.message,'innerAttr','DJIOW')
this.showDiv=!this.showDiv;
}
}
})
</script>
异步更新队列
当我们对Vue的属性做出改变时,页面并非立即刷新渲染,而是将这些改变放入一个队列。在下一次事件循环‘tick’中,刷新队列并执行实际工作,把对数据的改变渲染到页面上。可是有时候我们需要在Dom改变后做出一些操作,这时可以使用Vue.nextTick(callback),回调函数会在DOM更新完成后调用。如下例
若是在组件内部,则通过this.$nextTick()来实现。其中this为当前vue组件实例
<body>
<div id="test">
<div>{{message}}</div>
</child>
</div>
</body>
<script type="text/javascript">
var vueTest=new Vue({
el:'#test',
data:{
message:''
} })
vueTest.message='i am new message'
debugger//此时会看到,页面并未更新,显示的仍未空字符串
</script>
<body>
<div id="test">
<div>{{message}}</div>
</child>
</div>
</body>
<script type="text/javascript">
var vueTest=new Vue({
el:'#test',
data:{
message:''
} })
vueTest.message='i am new message'
Vue.nextTick(function(){
debugger//此时会看到,页面已经更新,显示的为新字符串
})
</script>
若是在组件内部,则通过this.$nextTick()来实现。其中this为当前vue组件实例
<body>
<div id='test'>
<child>
</child>
</div>
</div>
</body>
<script type="text/javascript">
Vue.component('child',{
template:'<div style="background:red;width:100px;height:100px" v-on:click="clickDiv">{{message}}</div>',
data:function(){return {message:''}},
methods:{
clickDiv:function(){
this.message=' i am clicked'
this.$nextTick(function(){
debugger
})
}
}
})
var vueTest=new Vue({
el:'#test',
})
</script>
Render函数
render函数用来通过js生成DOM。这在有些情况下是需要的。比如,我现在想要根据level值,生成指定大小的字体。
<body>
<div id='test'>
<anchored-heading :level="2">Hello world!</anchored-heading>
</div>
</div>
<template id="anchored-heading-template">
<div >
<h1 v-if="level === 1">
<slot></slot>
</h1>
<h2 v-if="level === 2">
<slot></slot>
</h2>
<h3 v-if="level === 3">
<slot></slot>
</h3>
<h4 v-if="level === 4">
<slot></slot>
</h4>
<h5 v-if="level === 5">
<slot></slot>
</h5>
<h6 v-if="level === 6">
<slot></slot>
</h6>
</div>
</template>
</body>
<script type="text/javascript">
Vue.component('anchored-heading', {
template: '#anchored-heading-template',
props: {
level: {
type: Number,
required: true
}
}
})
var vueTest=new Vue({
el:'#test'
})
</script>
<body>
<div id='test'>
<anchored-heading :level="3">Hello world!</anchored-heading>
</div>
</div>
</body>
<script type="text/javascript">
Vue.component('anchored-heading', {
render:function(createElement){
return createElement('h'+this.level,this.$slots.default)//(html标签,标签内的内容)
},
props: {
level: {
type: Number,
required: true
}
}
})
var vueTest=new Vue({
el:'#test' })
</script>
自定义指令
钩子函数
bind: 只调用一次,指令第一次绑定到元素时调用,用这个钩子函数可以定义一个在绑定时执行一次的初始化动作。通常用于初始化被绑定元素。
<body>
<div id='test'>
<div v-color='colorMessage' style='width: 100px;height: 100px'>
i am message
</div> </div>
</div>
</body>
<script type="text/javascript">
Vue.directive('color', {
bind:function(el,binding){
el.style.backgroundColor=binding.value;//若没有对bind事件的回调,则对colorMessage变动之前标签颜色不会发生变化,即页面初始化时不会变为红色。
},
update:function(el,binding){
el.style.backgroundColor=binding.value;
}
})
var vueTest=new Vue({
el:'#test',
data:{colorMessage:'red'}
})
</script>
混合
Vue中的一个属性:mixins用来声明混合的对象。当所混合的对象与Vue实例自身的选项相重合时,混合对象的配置项和Vue实例的配置项相结合一起执行,且混合对象的执行优先级要高于vue实例。如:
<body>
<div id='vueDemo'><input >
</input>
</div>
</body>
<script type="text/javascript">
// 定义一个混合对象
var myMixin = {
created: function () {//created为一个钩子函数
this.hello()
},
methods: {
hello: function () {
console.log('hello from mixin!')
}
}
}
var vueTest=new Vue({
el:'#vueDemo',
mixins:[myMixin],//注意,这里是数组
created:function(){
console.log('from vue instance')//由于vue实例和myMixin混合对象都定义了created钩子函数,故这两个钩子函数中的操作都将被执行。最终的结果是在控制台输出了hello from mixin和from vue instance
}
})
</script>
当一级选项内部的二级属性名相重合时,并非全部执行,而是执行组件的操作,而不执行vue实例的。如:methods内定义的方法相重合时。
<body>
<div id='vueDemo'><input >
</input>
</div>
</body>
<script type="text/javascript">
// 定义一个混合对象
var myMixin = {
created: function () {
this.hello()
},
methods: {
hello: function () {
console.log('hello from mixin!')
}
}
}
var vueTest=new Vue({
el:'#vueDemo',
mixins:[myMixin],
methods:{hello:function(){
console.log( 'a hello from vue')//运行后输出 a hello from vue和from vue instance。而不输出hello from mixin
}},
created:function(){
console.log('from vue instance')
}
})
</script>