vue作为时下最流行的mvvm框架之一,相信不少前端的小伙伴们对vue绝不陌生。vue既小又快,渐进式项目功能,而且较为容易上手, 加上社区的大力开源支持,从一出生就站在巨人肩膀上的vue成为了潮流的明星。
但是,vue是怎么做到了数据的绑定,dom'的更新呢?这方面我想你一定想知道,但是又不愿意去看源码,所以这篇文章,事实上值得你读一读。
首先,我们来看看vue官方文档第一句:
Vue.js(读音 /vjuː/, 类似于 view) 是一套构建用户界面的 渐进式框架。
把渐进式这三个字圈起来,划重点,考试要考。那这时你可能就会有疑问了,什么叫渐进式?什么叫他妈的渐进式?他妈的什么叫他妈的渐进式?其实很简单,渐进式你可以理解为:我想要用vue,但我不必要使用它的全家桶,我想用多少就用多少,比如我有一个react项目,我想在里面用vue搞搞,我可以先用五分之一,觉得不错,再用五分之二,用到满意为止。
这就是渐进式,简而言之,就是可以选择不完全使用它,而是渐渐地使用它,渐渐地深入它。现在你明白了吧?是不是觉得,我靠,原来渐进式是个这么玩意,这跟jquery有毛区别,简直就是low B。事实上这么想虽然有点粗鲁,但是确实很正确的。我现在发现很多前端的新人对jquery嗤之以鼻,反而vue,react什么的奉如祖宗,面试动不动就来一句,“jquery已经过世了,这种mvm框架本来就应该被淘汰掉”,可是年轻人,你知不知道页面任何数据的改变都要操作dom来进行实现的,你之所以在vue里看不到任何有关dom的操作,只是因为vue已经帮你封装了。
那么,新的问题又来了,vue是如何实现数据的更新变化的呢?
来看文档里的这两句话:
把一个普通 Javascript 对象传给 Vue 实例的 data
选项,Vue 将遍历此对象所有的属性,并使用 Object.defineProperty 把这些属性全部转为 getter/setter。
getter/setter 对于用户来说是不可见的,但是基于此内部机制,可以使 Vue 在属性被访问和修改时去触发相应的 getter 和 setter,以实现依赖追踪(dependency-track)和变更通知(change-notification)。
事实上,这两句话已经把vue框架的实现机制说得明明白白。但是非常有必要的,让我们再次来理解一下这两句话。
第一句话其实显得并不严谨,我用我自己的话翻译过来是这样的:在vue进行ceated之后,任何定义在data上的变量都将被转化成getter/setter,同时将产生一个监听器监听这些变量的行踪和变化,在数据unpdated之后,先执行setter去更新data里的属性,然后在用getter获取到data里的属性,进而去改变dom上的数据展示。
事实上你根本可以把vue当作一个类,只不过他的成员变量都是静态的,并且给他设置了一个监听器,用以监听这些数据的变化以及定义处理事件。有代码来表示他们大致是这样子的:
public class Vue{
public static type vaiate1;
public static type vaiate2;
public static type vaiate3;
public static type vaiate4;
……
public static type vaiate5;
private type setter1(){
}
private static getter1(){
}
private type setter2(){
}
private static getter2(){
}
private type setter3(){
}
private static getter3(){
}
private type setter14(){
}
private static getter4(){
}
......
private type settern(){
}
private static gettern{
}
public type Vue{
}
}
Class Observer{
//监听数据变化
}
class handle{
//操作数据变化
}
这样看来,大家是不是更一目了然一些呢?
好的,现在大家理解了vue是如何实现数据的更新变化的,让我们进行下一步。在此之前我要声明,上述代码并非vue真正实现代码,我只是为了让我的逻辑更加清晰一点。如果你对此感兴趣,建议去查看vue的源代码。
因为vue基于data的数据响应式绑定的原理,所以当你动态地给vue添加新属性时,vue不能监听到它的变化。比如:
var vm = new Vue({
data:{
a:1
}
})
// `vm.a` 是响应的,因为在created之前就在data里面进行定义了
vm.b = 2
// `vm.b` 是非响应的,因为它虽然是在data上添加的,但是是在mounted之后才进行添加的,没有生成getter和setter
所以在文档里我们可以看到这句话:
由于 Vue 不允许动态添加根级响应式属性,所以你必须在初始化实例前声明根级响应式属性,哪怕只是一个空值:
这时候可能有的小伙伴发脾气了,不行,我就是要在vue的根上加上一个属性,我还要让他是响应式的。言外之意就是想补一班车,让我定义的变量在vue实例化之后仍然可以拥有setter/getter的能力。vue也提供了这类办法,就是你可以在嵌套的对象里面加上一个响应式属性,卧槽,是不是感动得要哭了:
var vm = new Vue({
data:{
a:{//此处的a是一个嵌套的对象
"message":“hello”
}
}
})
Vue.set(vm.a, 'messageLate', ",world");//前提,a是一个嵌套的对象
但是有的同学让然不满意,觉得我靠,我真的受不了做a的儿子,我一定要和它平起平坐。我王境泽就是死,也要实现这个东西:
var vm = new Vue({
data:{
a:1
}
})
Vue.set(vm.a, 'b',1);
//然后data就变成了这样
data:{
a:1,
b:2
}
对不起,vue没有给我们这样的权利。但是在项目里面,往往需要给一个数据对象加上一些新的属性,这时你可以新建一个变量,调用类似
this.someObject = Object.assign({}, this.someObject, { a: 1, b: 2 })
给对象加上一个新的属性,但是缺点是它不是响应级的属性。
最后的最后,就是关于vue的异步更新队列的问题。vue并不是检测到数据的变化就去更新一个数据的,因为考虑到性能的问题,尽量避免不必要的数据更新。所以,数据变化时,vue不会马上更新,而是开启一个队列,把所有的更新都丢进去,在一段周期的去重之后,再对按照队列依次进行数据的更新操作。所以在实际的项目中会发现这样的问题,我修改了数据,但是数据没有发生改变,导致我取的值不正确,dom也没有发生变化。
这时,你很难去更改vue更新队列,只好使用函数Vue.nextTick(callback),这个函数不会影响到更新队列,但是在回调函数里你可以随心所欲地直接控制dom进行数据改变,而不必等到更新队列执行完毕。