一、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>
三、组件之间参数传递 + Vuex
父->子: prop方法,父组件在子组件标签里写好要传的数据,子组件在自己的data里接收
子->父: $emit方法,在自己的method里提交$emit
兄<->弟: 创建一个事件中心 eventBus
也可以直接用vuex,但在逻辑简单的单页应用里使用简单的store模式就可以
Vuex
专为 Vue.js 应用程序开发的状态管理模式,可以控制组件状态以你期望的方式进行更新
包含5个核心属性:
1、state 是一个用来储存数据的仓库
2、getter 对state中的数据计算,相当于state的计算属性
3、mutation 所有的数据更新操作最终必须由触发mutation中的方法实现
4、action 可写异步函数提交到mutation,通过store.dispath来分发action
5、module 把store拆分成易于项目业务和维护的模块
具体用法参考这里
$store.dispatch
$store.commit
待添加
四、关于Vue-router
Vue.js的官方路由管理器
分为 hash 模式和 history 模式
1、hash: url里#+#后面的部分就是hash的值,这部分的值不会被包括在http请求里进行提交,原理是 onhashchange 事件,hash是vue-router的默认方式(嫌丑才用history?)
2、history: 利用 history.pushState 、history.replaceState API 来完成 URL 跳转而无须重新加载页面。提交http请求时url全部会被提交,因此需要后台配合对每一个值进行应对,匹配一个404页面这样子。
2、Vue 路由中 route 和router 的区别
route是“路由信息对象”,包括path,params,hash,query,fullPath,matched,name等路由信息参数。
router是“路由实例”对象包括了路由的跳转方法,钩子函数等。
3、上一条中的钩子函数(导航守卫)
在路由发生变化时需要进行一些特殊处理,就要用到钩子函数。Vue-router的钩子函数大致分为三类
1)全局钩子
beforeEach 和 afterEach
const router = new VueRouter({
mode: 'history',
base: __dirname,
routes: routerConfig
})
router.beforeEach((to, from, next) => {
document.title = to.meta.title || 'demo'
if (!to.query.url && from.query.url) {
to.query.url = from.query.url
}
next()
})
router.afterEach(route => {
})
2)单个路由里面的钩子
如果你不想全局配置守卫的话,你可以为某些路由单独配置守卫:
const router = new VueRouter({
routes: [
{
path: '/foo',
component: Foo,
beforeEnter: (to, from, next) => {
// 参数用法什么的都一样,调用顺序在全局前置守卫后面,所以不会被全局守卫覆盖
// ...
}
}
]
3)组件内钩子
路由组件:直接定义在router中component处的组件。
在组件内,和data、method平级的方法使用
beforeRouteLeave(to, from, next) {
/* 在路由独享守卫后调用 不!能!获取组件实例 `this`,组件实例还没被创建*/
next()
},
beforeRouteEnter(to, from, next) {
/* 在当前路由改变,但是该组件被复用时调用 可以访问组件实例 `this`
举例来说,对于一个带有动态参数的路径 /foo/:id,在 /foo/1 和 /foo/2 之间跳转的时候,
由于会渲染同样的 Foo 组件,因此组件实例会被复用。而这个钩子就会在这个情况下被调用。*/
next()
},
beforeRouteUpdate(to, from, next) {
/* 导航离开该组件的对应路由时调用,可以访问组件实例 `this`*/
next()
},
computed: {},
method: {}
不记得用法了就看一下这个博客
三种路由钩子中都涉及到了三个参数:(先记住参数吧 啥也看不懂)
to: Route: 即将要进入的目标路由对象
from: Route: 当前导航正要离开的路由
next: Function: 一定要调用该方法来 resolve 这个钩子。执行效果依赖 next 方法的调用参数。
-> next(): 进行管道中的下一个钩子。如果全部钩子执行完了,则导航的状态就是 confirmed (确认的)。
->next(false): 中断当前的导航。如果浏览器的 URL 改变了 (可能是用户手动或者浏览器后退按钮),那么 URL 地址会重置到 from 路由对应的地址。
->next(‘/’) 或者 next({ path: ‘/’ }): 跳转到一个不同的地址。当前的导航被中断,然后进行一个新的导航。
->next(error): (2.4.0+) 如果传入 next 的参数是一个 Error 实例,则导航会被终止且该错误会被传递给 router.onError() 注册过的回调.