让我们来回顾下Vue的介绍,可以发现Vue最显著的一个功能是响应系统。那么它的实现原理有又是如何呢?
如何追踪变化
把一个普通Javascript对象传给Vue实例来作为它的data选项,Vue将遍历它的属性,用Object.defineProperty将它们转为getter/setter。这是ES5的特性,不能打补丁实现,这便是为什么Vue不支持IE8以及更低版本浏览器的原因。
用户看不到getter/setters,但是在内部它们让Vue追踪依赖,在属性被访问和修改时通知变化。这里需要注意的问题是浏览器控制台在打印数据对象时getter/setter的格式化并不同,所以你可能需要安装vue-devtools来获取更加友好的检查接口。
每个组件实例都有相应的watcher程序实例,它会在组件渲染的过程中把属性记录为依赖,之后当依赖项的setter被调用时,会通知watcher重新计算,从而致使它关联的组件得以更新。
变化检测问题
受现代Javascript的限制(以及Object.observe的废弃),Vue不能检测到对象属性的添加或删除。因为Vue在初始化实例时将属性转为getter/setter,所以属性必须在data对象上才能让Vue转换它,这样才能让它是响应的。例如:
Vue不允许在已经创建的实例上动态添加新的根级响应式属性(root-levelreactiveproperties)。然而它可以使用Vue.set(object,key,value)方法将响应属性添加到嵌套的对象上:
您还可以使用vm.set实例方法,这也是全局Vue.set方法的别名:
有时你想向已有对象上添加一些属性,例如使用Object.assign()或_.extend()方法来添加属性。但是,添加到对象上的新属性不会触发更新。在这种情况下可以创建一个新的对象,让它包含原对象的属性和新的属性:
也有一些数组相关的问题,之前已经在列表渲染中讲过。
由于Vue不允许动态添加根级响应式属性,所以你必须在初始化实例前声明根级响应式属性,哪怕只是一个空值:
如果你不在data对象中声明message,Vue将发出警告表明你的渲染方法正试图访问一个不存在的属性。
这样的限制在背后是有其技术原因的,在依赖项跟踪系统中,它消除了一类边界情况,也使Vue实例在类型检查系统的帮助下运行的更高效。在代码可维护性方面上这也是重要的一点:data对象就像组件状态的模式(Schema),在它上面声明所有的属性让组织代码更易于被其他开发者或是自己回头重新阅读时更加快速地理解。
异步更新队列
你应该注意到Vue执行DOM更新是异步的,只要观察到数据变化,Vue就开始一个队列,将同一事件循环内所有的数据变化缓存起来。如果一个watcher被多次触发,只会推入一次到队列中。然后,在接下来的事件循环中,Vue刷新队列并仅执行必要的DOM更新。Vue在内部使用Promise.then和MutationObserver为可用的异步队列调用回调setTimeout(fn,0).
例如,当你设置vm.someData=‘newvalue’,该组件不会马上被重新渲染。当刷新队列时,这个组件会在下一次事件循环清空队列时更新。我们基本不用关心这个过程,但是如果你想在DOM状态更新后做点什么,这就可能会有些棘手。一般来讲,Vue鼓励开发者沿着数据驱动的思路,尽量避免直接接触DOM,但是有时我们确实要这么做。为了在数据变化之后等待Vue完成更新DOM,可以在数据变化之后立即使用Vue.nextTick(callback)。这样回调在DOM更新完成后就会调用。例如:
vm.nextTick()这个实例方法在组件内使用特别方便,因为它不需要全局Vue,它的回调this将自动绑定到当前的Vue实例上: