一、Vue生命周期
浅级别部分
一个Vue 实例从创建到销毁的过程,就是生命周期。从开始创建、初始化数据、编译模板、挂载Dom→渲染、更新→渲染、销毁等一系列过程,称之为 Vue 的生命周期。
首先四个阶段很好记:
- 创建 create
- 挂载 mount
- 更新 update
- 销毁 destroy
每个阶段都有xx前(before+)、xx后(+ed)两部分,一共八个生命周期。
beforeCreate 在数据观测和初始化事件还未开始
created 完成一些属性和方法的运算,初始化事件
beforeMount 编译模板,把data里面的数据和模板生成html。但还没有挂载到页面上
mounted 渲染到html到页面,会进行ajax交互(dom渲染)
beforeUpdate 发生在虚拟DOM重新渲染和打补丁之前,可以在该钩子中进一步地更改状态,不会触发附加的重渲染过程。
updated 组件DOM已经更新,应该避免在此期间更改状态,因为这可能会导致更新无限循环。该钩子在服务器端渲染期间不被调用。
beforeDestroy 此时实例仍然完全可用
destroyed 所有的事件监听器、子实例会被销毁移除。该钩子在服务器端渲染期间不被调用。
1、生命周期中有多个事件钩子,帮助我们控制Vue实例的过程时更容易形成好的逻辑。
2、第一次加载页面触发(前四个) beforeCreate, created, beforeMount, mounted
待添加...
深级别部分 ###(水平不够先越过这里 这里只做记录)
关于create(初始化)
在初始化时,会调用以下代码,生命周期就是通过 callHook 调用的
Vue.prototype._init = function(options) { initLifecycle(vm) initEvents(vm) initRender(vm) callHook(vm, 'beforeCreate') // 拿不到 props data initInjections(vm) initState(vm) initProvide(vm) callHook(vm, 'created')}
beforeCreate 调用的时候,是获取不到 props 或者 data 中的数据的,因为这些数据的初始化都在 initState 中。
关于mount(挂载函数)
export function mountComponent { callHook(vm, 'beforeMount') // ... if (vm.$vnode == null) { vm._isMounted = true callHook(vm, 'mounted') }}
beforeMount 就是在挂载前执行的,然后开始创建 VDOM 并替换成真实 DOM,最后执行 mounted 钩子。这里会有个判断逻辑,如果是外部 new Vue({}) 的话,不会存在 $vnode ,所以直接执行 mounted 钩子了。如果有子组件的话,会递归挂载子组件,只有当所有子组件全部挂载完毕,才会执行根组件的挂载钩子。
关于update
function flushSchedulerQueue() { // ... for (index = 0; index < queue.length; index++) { watcher = queue[index] if (watcher.before) { watcher.before() // 调用 beforeUpdate } id = watcher.id has[id] = null watcher.run() // in dev build, check and stop circular updates. if (process.env.NODE_ENV !== 'production' && has[id] != null) { circular[id] = (circular[id] || 0) + 1 if (circular[id] > MAX_UPDATE_COUNT) { warn( 'You may have an infinite update loop ' + (watcher.user ? `in watcher with expression "${watcher.expression}"` : `in a component render function.`), watcher.vm ) break } } } callUpdatedHooks(updatedQueue)}function callUpdatedHooks(queue) { let i = queue.length while (i--) { const watcher = queue[i] const vm = watcher.vm if (vm._watcher === watcher && vm._isMounted) { callHook(vm, 'updated') } }}
上图还有两个生命周期没有说,分别为 activated 和 deactivated ,这两个钩子函数是 keep-alive 组件独有的。用 keep-alive 包裹的组件在切换时不会进行销毁,而是缓存到内存中并执行 deactivated 钩子函数,命中缓存渲染后会执行 actived 钩子函数。
关于destroy
Vue.prototype.$destroy = function() { // ... callHook(vm, 'beforeDestroy') vm._isBeingDestroyed = true // remove self from parent const parent = vm.$parent if (parent && !parent._isBeingDestroyed && !vm.$options.abstract) { remove(parent.$children, vm) } // teardown watchers if (vm._watcher) { vm._watcher.teardown() } let i = vm._watchers.length while (i--) { vm._watchers[i].teardown() } // remove reference from data ob // frozen object may not have observer. if (vm._data.__ob__) { vm._data.__ob__.vmCount-- } // call the last hook... vm._isDestroyed = true // invoke destroy hooks on current rendered tree vm.__patch__(vm._vnode, null) // fire destroyed hook callHook(vm, 'destroyed') // turn off all instance listeners. vm.$off() // remove __vue__ reference if (vm.$el) { vm.$el.__vue__ = null } // release circular reference (##6759) if (vm.$vnode) { vm.$vnode.parent = null }}
在执行销毁操作前会调用 beforeDestroy 钩子函数,然后进行一系列的销毁操作,如果有子组件的话,也会递归销毁子组件,所有子组件都销毁完毕后才会执行根组件的 destroyed 钩子函数。
二、Vue的双向绑定原理
数据劫持+发布-订阅者模式
广义双向绑定的描述就是 数据变化更新视图,视图变化更新数据
view->data:通过简单监听事件即可
data->view:通过Object.defineProperty() 来给属性设置 set(设置属性值时触发)和get(读取属性值时触发)函数,当数据改变了就会来触发这个函数,所以我们只要将一些需要更新的方法放在这里面就可以实现data更新view了
Object.defineProperty()可以劫持各个属性的setter,getter,当数据变动时发布消息给订阅者,触发相应监听回调。当把一个普通 Javascript 对象传给 Vue 实例来作为它的 data 选项时,Vue 将遍历它的属性,用 Object.defineProperty 将它们转为 getter/setter。在内部它们让 Vue 追踪依赖,在属性被访问和修改时通知变化。
vue的数据双向绑定 整合Observer,Compile和Watcher三者(不能完全理解的部分)
- Observer来监听自己的model的数据变化
- Compile来解析编译模板指令
- watcher搭起observer和Compile之间的通信桥梁
example,使用js实现一个简单的双向绑定(理解)
<body> <div id="app"> <input type="text" id="txt"> <p id="show"></p></div></body><script type="text/javascript"> var obj = {} Object.defineProperty(obj, 'txt', { get: function () { return obj }, set: function (newValue) { document.getElementById('txt').value = newValue document.getElementById('show').innerHTML = newValue } }) document.addEventListener('keyup', function (e) { obj.txt = e.target.value })</script>