Vue的响应式
数据驱动视图:数据变化了,做一些事情让视图改变。
数据劫持:当数据被访问的时候,进行拦截,此时会进行依赖收集,数据被改变你的时候,会发送通知,告诉依赖下的watcher该执行了。
依赖收集:new watcher()中,其中会传入vm._update(vm._render())函数,在watcher中执行vm._update(vm._render()),在_render当中会访问数据,然后会被劫持,最后触发依赖收集的过程。
通知依赖:数据改变,通知依赖执行对应的渲染函数,即vm._update(vm._render()),更新视图。
关键的点:
1.对于一个对象,会递归对象,把嵌套对象(数组)以及对象中的每一个属性(数组中每一个元素,如果这个元素为对象)都变为响应式。
为什么对象本身也要变为响应式呢?(即对象本身也有一个dep对象)因为这样后面可以根据Vue.set,Vue.delete为该对象添加或者删除属性的时候可以发送知。
2.对于数组,改写了数组的push,pop,splice等可以改变原数组的方法,在保持数组原有API功能不变的情况下,增加了一个发送通知的功能。那么Vue.set,Vue.delete方法对于数组本身,其实内部就是采用了重写后的splice方法去修改数组。
3.关于vue中异步队列:首先明白一点就是数据改变后,视图是不会同步渲染的,这样开销太大。假设我使用this.msg = 1修改数据之后,下一行使用this.msg = 2再次修改数据,那么这样的话vm._update(vm._render())要执行两次,显然是不太合理的。所以在Vue中,会把需要执行的watcher放入队列当中,等到所有同步的代码执行之后,在调用这个队列中的watcher进行视图的更新。在这个队列中会进行watcher去重,因为像上面的例子,msg修改了两次,就要执行两次相同的watcher,显然是不符合道理的,所以会有一个去重的过程。其次watcher还会按id从小到大排列,因为我们在创建父子组件的时候,是先父后子,id小的为父,id大的为子,所以要进行一个从小到大的排列。
4.关于nextTick,其实就是往异步队列中push一个自定义的方法,为什么说能够在nextTick中能够拿到最新dom的数据呢?举个例子:在这个例子中
直接console.log(this.$refs.p1.textContent)是无法拿到数据的,因为这是同步代码,对于的watcher还没开始执行,dom未更新视图未渲染,所以无法拿到dom上最新的数据,如果在nextTick中却可以,因为这样的话是在队列中,也就是watcher的后面添加了一个任务,watcher执行完了才轮到这个任务,那么自然能够拿到最新的数据了。另外,这个任务队列是以微任务的方式去执行的。
mounted() {
this.msg = "Hello World";
this.name = "Hello snabbdom";
this.title = "Vue.js";
console.log(this.$refs.p1.textContent); // 旧的数据
Vue.nextTick(() => {
console.log(this.$refs.p1.textContent); // 新的数据
});
},