1. Vue生命周期
生命周期函数,就是在某个时刻会自动执行的函数。 每个 Vue 实例在被创建时都要经过一系列的初始化过程——例如,需要设置数据监听、编译模板、将实例挂载到 DOM 并在数据变化时更新 DOM 等。在这个过程中也会运行一些叫做生命周期钩子的函数(监听函数)。
八大生命周期钩子函数:
| 函数 | 调用时间 |
|---|---|
| beforeCreate | vue实例初始化之前调用 |
| created | vue实例初始化之后调用 |
| beforeMount | 挂载到DOM树之前调用 |
| mounted | 挂载到DOM树之后调用 |
| beforeUpdate | 数据更新之前调用 |
| updated | 数据更新之后调用 |
| beforeDestroy | vue实例销毁之前调用 |
| destroyed | vue实例销毁之后调用 |
<div id="app">
{{information}}
</div>
<script type="text/javascript">
//创建vue实例
var vm = new Vue({
el: '#app',
data: {
information: '北极光之夜。'
}
})
// 各个生命周期函数通过调用下面这个函数了解其所处的生命阶段
function show(inf,obj){
console.log(inf);
console.log("------------------------------------------");
console.log('获取vue实例data里的数据:');
console.log(obj.information);
console.log("------------------------------------------");
console.log('挂载的对象,就是DOM:');
console.log(obj.$el);
console.log("------------------------------------------");
console.log('页面上已经挂载的DOM:');
console.log(document.getElementById('app').innerHTML);
}
</script>
考察3部分数据:
1.Vue实例中的data:obj.information
2.Vue实例中挂载的对象:obj.$el
3.页面上已经挂载了的DOM:document.getElementById('app').innerHTML
1. beforeCreated
vue实例刚刚在内存中创建,此时data和methods这些都没初始化好。
obj.information: undefined
obj.$el: undefined
document.getElementById('app').innerHTML: {{information}}
2. created
vue实例已经创建好,data和methods都有,模板还没编译。
obj.information: 北极光之夜
obj.$el: undefined
document.getElementById('app').innerHTML: {{information}}
3. beforeMount
完成了模板的编译,但是还没挂载到页面DOM上,这阶段页面还没显示出来。
obj.information: 北极光之夜
obj.$el: <div id="app"> {{information}} </div>
document.getElementById('app').innerHTML: {{information}}
4. mounted
模板编译好了,也挂载到页面中了,页面也可以显示了。
obj.information: 北极光之夜
obj.$el: <div id="app"> 北极光之夜 </div>
document.getElementById('app').innerHTML: 北极光之夜
后略………………
| 生命周期钩子 | 组件状态 | 最佳实践 |
|---|---|---|
| beforeCreate | 实例初始化之后,this指向创建的实例,不能访问到data、computed、watch、methods上的方法和数据 | 常用于初始化非响应式变量 |
| created | 实例创建完成,可访问data、computed、watch、methods上的方法和数据,未挂载到DOM,不能访问到ref属性内容为空数组 | 常用于简单的ajax请求,页面的初始化 |
| beforeMount | 在挂载开始之前被调用,beforeMount之前,会找到对应的template,并编译成render函数 | – |
| mounted | 实例挂载到DOM上,此时可以通过DOM API获取到DOM节点,$ref属性可以访问 | 常用于获取VNode信息和操作,ajax请求 |
| beforeupdate | 响应式数据更新时调用,发生在虚拟DOM打补丁之前 | 适合在更新之前访问现有的DOM,比如手动移除已添加的事件监听器 |
| updated | 虚拟 DOM 重新渲染和打补丁之后调用,组件DOM已经更新,可执行依赖于DOM的操作 | 避免在这个钩子函数中操作数据,可能陷入死循环 |
| beforeDestroy | 实例销毁之前调用。这一步,实例仍然完全可用,this仍能获取到实例 | 常用于销毁定时器、解绑全局事件、销毁插件对象等操作 |
| destroyed | 实例销毁后调用,调用后,Vue 实例指示的所有东西都会解绑定,所有的事件监听器会被移除,所有的子实例也会被销毁 | – |
beforeCreate:可用于初始化非响应式变量
created:常用于简单的ajax请求,页面的初始化
beforeMount:无法获取dom
mounted:常用于获取VNode信息和操作,ajax请求
Vue 通过建立一个虚拟 DOM 来追踪自己要如何改变真实 DOM。请仔细看这行代码:
return createElement('h1', this.blogTitle)
createElement到底会返回什么呢?其实不是一个实际的 DOM 元素。它更准确的名字可能是createNodeDescription,因为它所包含的信息会告诉 Vue 页面上需要渲染什么样的节点,包括及其子节点的描述信息。我们把这样的节点描述为“虚拟节点 (virtual node)”,也常简写它为“VNode”。“虚拟 DOM”是我们对由 Vue 组件树建立起来的整个 VNode 树的称呼。
beforeUpdate:适合在更新之前访问现有的DOM,比如手动移除已添加的事件监听器
updated:避免在这个钩子函数中操作数据,可能陷入死循环
beforeDestroy:常用于销毁定时器、解绑全局事件、销毁插件对象等操作
注意:
created阶段的ajax请求与mounted请求的区别:前者页面视图未出现,如果请求信息过多,页面会长时间处于白屏状态mounted不会承诺所有的子组件也都一起被挂载。如果你希望等到整个视图都渲染完毕,可以用 vm.$nextTick
2.Vue修饰符
lazy修饰符作用是,改变输入框的值时value不会改变,当光标离开输入框时,v-model绑定的值value才会改变
trim修饰符的作用类似于JavaScript中的trim()方法,作用是把v-model绑定的值的首尾空格给过滤掉。
number修饰符的作用是将值转成数字,但是先输入字符串和先输入数字,是两种情况
stop修饰符的作用是阻止冒泡
capture:事件默认是由里往外冒泡,capture修饰符的作用是反过来,由外网内捕获
self修饰符作用是,只有点击事件绑定的本身才会触发事件
once修饰符的作用是,事件只执行一次
prevent修饰符的作用是阻止默认事件(例如a标签的跳转)
native修饰符是加在自定义组件的事件上,保证事件能执行
left,right,middle是鼠标的左中右按键触发的事件
passive当我们在监听元素滚动事件的时候,会一直触发onscroll事件,在pc端是没啥问题的,但是在移动端,会让我们的网页变卡,因此我们使用这个修饰符的时候,相当于给onscroll事件整了一个.lazy修饰符
sync当父组件传值进子组件,子组件想要改变这个值时,可以这么做
父组件里
<children :foo="bar" @update:foo="val => bar = val"></children>
子组件里
this.$emit('update:foo', newValue)
复制代码
sync修饰符的作用就是,可以简写:
父组件里
<children :foo.sync="bar"></children>
子组件里
this.$emit('update:foo', newValue)
keyCode当我们这么写事件的时候,无论按什么按钮都会触发事件
<input type="text" @keyup="shout(4)">
复制代码
那么想要限制成某个按键触发怎么办?这时候keyCode修饰符就派上用场了
<input type="text" @keyup.keyCode="shout(4)">
复制代码
Vue提供的keyCode:
//普通键
.enter
.tab
.delete //(捕获“删除”和“退格”键)
.space
.esc
.up
.down
.left
.right
//系统修饰键
.ctrl
.alt
.meta
.shift
3.Vue指令Directives
指令 (Directives) 是带有 v- 前缀的特殊 attribute。
指令 attribute 的值预期是单个 JavaScript 表达式 (v-for 是例外情况,稍后我们再讨论)。
指令的职责是,当表达式的值改变时,将其产生的连带影响,响应式地作用于 DOM。
- v-text
- v-html
- v-show
- v-if
- v-else
- v-else-if
- v-for
- v-on
- v-bind
- v-model
- v-slot
- v-once
- v-pre
- v-cloak
4.组件间通信
-
父组件传值给子组件,子组件使用
props进行接收 -
子组件传值给父组件,子组件使用
$emit+事件对父组件进行传值 -
组件中可以使用
$parent和$children获取到父组件实例和子组件实例,进而获取数据 -
使用
$attrs和$listeners,在对一些组件进行二次封装时可以方便传值,例如A->B->C -
使用
$refs获取组件实例,进而获取数据 -
使用
Vuex进行状态管理 -
使用
eventBus进行跨组件触发事件,进而传递数据 -
使用
provide和inject,官方建议我们不要用这个,我在看ElementUI源码时发现大量使用 -
使用浏览器本地缓存,例如
localStorage
5.路由模式
- hash模式:通过
#号后面的内容的更改,触发hashchange事件,实现路由切换 - history模式:通过
pushState和replaceState切换url,实现路由切换,需要后端配合
6.v-if和v-show有何区别?
- 1.
v-if是通过控制dom元素的删除和生成来实现显隐,每一次显隐都会使组件重新跑一遍生命周期,因为显隐决定了组件的生成和销毁 - 2.
v-show是通过控制dom元素的css样式来实现显隐,不会销毁 - 3.频繁或者大数量显隐使用
v-show,否则使用v-if
7.computed和watch有何区别?
-
1.
computed是依赖已有的变量来计算一个目标变量,大多数情况都是多个变量凑在一起计算出一个变量,并且computed具有缓存机制,依赖值不变的情况下其会直接读取缓存进行复用,computed不能进行异步操作 -
2.
watch是监听某一个变量的变化,并执行相应的回调函数,通常是一个变量的变化决定多个变量的变化,watch可以进行异步操作 -
3.简单记就是:一般情况下
computed是多对一,watch是一对多
8.vuex的有哪些属性?用处是什么?
-
State:定义了应用状态的数据结构,可以在这里设置默认的初始状态。
-
Getter:允许组件从 Store 中获取数据,mapGetters 辅助函数仅仅是将 store 中的 getter 映射到局部计算属性。
-
Mutation:是唯一更改 store 中状态的方法,且必须是同步函数。
-
Action:用于提交 mutation,而不是直接变更状态,可以包含任意异步操作。
-
Module:允许将单一的 Store 拆分为多个 store 且同时保存在单一的状态树中。
9.父子组件生命周期顺序
父beforeCreate -> 父created -> 父beforeMount -> 子beforeCreate -> 子created -> 子beforeMount -> 子mounted -> 父mounted
10.说说nextTick的用处?
在下次 DOM 更新循环结束之后执行延迟回调。在修改数据之后立即使用这个方法,获取更新后的 DOM。
简单来说,Vue 在修改数据后,视图不会立刻更新,而是等同一事件循环中的所有数据变化完成之后,再统一进行视图更新。
<div ref="testDiv">{{name}}</div>
name: '小林'
this.name = '林三心'
console.log(this.$refs.testDiv.innerHTML) // 这里是啥呢
复制代码
答案是“小林”,前面说了,Vue是异步更新,所以数据一更新,视图却还没更新,所以拿到的还是上一次的旧视图数据,那么想要拿到最新视图数据怎么办呢?
this.name = '林三心'
this.$nextTick(() => {
console.log(this.$refs.testDiv.innerHTML) // 林三心
})
需要注意的是,在 created 和 mounted 阶段,如果需要操作渲染后的试图,也要使用 nextTick 方法。
11.Vue的SSR是什么?有什么好处?
SSR就是服务端渲染- 基于
nodejs serve服务环境开发,所有html代码在服务端渲染 - 数据返回给前端,然后前端进行“激活”,即可成为浏览器识别的html代码
SSR首次加载更快,有更好的用户体验,有更好的seo优化,因为爬虫能看到整个页面的内容,如果是vue项目,由于数据还要经过解析,这就造成爬虫并不会等待你的数据加载完成,所以其实Vue项目的seo体验并不是很好
12.Vue底层实现原理(双向绑定原理)
vue.js是采用数据劫持结合发布者-订阅者模式的方式,通过Object.defineProperty()来劫持各个属性的setter和getter,在数据变动时发布消息给订阅者,触发相应的监听回调。
13.vue中 route 的区别
| this.$route:当前激活的路由的信息对象。每个对象都是局部的,可以获取当前路由的 path, name, params, query 等属性。
| this.$router:全局的 router 实例。通过 vue 根实例中注入 router 实例,然后再注入到每个子组件,从而让整个应用都有路由功能。其中包含了很多属性和对象(比如 history 对象),任何页面也都可以调用其 push(), replace(), go() 等方法。
14.路由跳转的编程式和声明式
声明式:就是使用 router-link 组件来导航,通过传入 to 属性指定链接(router-link 默认会被渲染成一个a标签)。当需要在一个页面中嵌套子路由,并且页面不跳转的时候,只需要将子页面渲染在 router-view 里面就可以了。
编程式:想要导航到不同的 URL,可以使用 router.push 方法。这个方法会向 history 栈添加一个新的记录,所以,当用户点击浏览器后退按钮时,会回到之前的 URL。
当你点击 <router-link> 时,内部会调用这个方法,所以点击 <router-link :to="..."> 相当于调用 router.push(...) 。
router.push 和所有其他导航方法都会返回一个 Promise
15.引入路由的步骤
导入 VueRouter 并调用。
-
定义两个路由跳转的.vue 组件
-
导入vue-router,并安装路由
Vue.use(Router);
3. 创建 router 实例,并传递 routes 配置
const routes = [
{ path: '/home', component: Home },
{ path: '/user', component: User }
]
const router = new Router({
routes
})
4. 在 vue 根实例中注入路由
import router from './router'
new Vue({
router,
render: h => h(App)
}).$mount('#app')
this.$route:
其中 $route.matched 是一个数组,包含了当前路由的所有嵌套记录,即 routes 配置中的对象数组,包括 自己的信息和 children 数据。比如用导航守卫做登录功能时,若需要检测 meta 来判断是否需要登录的情况时,就可以通过遍历 $route.matched 来检查路由记录中的 meta 字段。
16.router-link的属性
- to
- replace
- active-class:链接激活时,应用于渲染的
<a>的 class。默认值:"router-link-active"
tag属性已作废,vue-router 4.x 使用以下方式处理:
<router-link> 中的 event 和 tag 属性都已被删除。你可以使用 v-slot API 来完全定制 <router-link>:
将
<router-link to="/about" tag="span" event="dblclick">About Us</router-link>
替换成
<router-link to="/about" custom v-slot="{ navigate }">
<span @click="navigate" @keypress.enter="navigate" role="link">About Us</span>
</router-link>
17.路由手动跳转方法和参数
this.$router.push,方法的参数可以是一个字符串路径,或者一个描述地址的对象。
const username = 'eduardo'
// 我们可以手动建立 url,但我们必须自己处理编码
router.push(`/user/${username}`) // -> /user/eduardo
// 同样
router.push({ path: `/user/${username}` }) // -> /user/eduardo
// 如果可能的话,使用 `name` 和 `params` 从自动 URL 编码中获益
router.push({ name: 'user', params: { username } }) // -> /user/eduardo
// `params` 不能与 `path` 一起使用
router.push({ path: '/user', params: { username } }) // -> /user
属性 to 与 router.push 接受的对象种类相同,所以两者的规则完全相同。
18.hash模式和history模式
hash —— 即地址栏 URL 中的 # 符号(此 hash 不是密码学里的散列运算)。比如这个 URL:www.abc.com/#/hello hash 的值为 #/hello。它的特点在于:hash 虽然出现在 URL 中,但不会被包括在 HTTP 请求中,仅 hash 符号之前的内容会被包含在请求中。对后端完全没有影响,因此改变 hash 不会重新加载页面。
history —— 利用了 HTML5 History Interface 中新增的 pushState() 和 replaceState() 方法。(需要特定浏览器支持)这两个方法应用于浏览器的历史记录栈,在当前已有的 back、forward、go 的基础之上,它们提供了对历史记录进行修改的功能。只是当它们执行修改时,虽然改变了当前的 URL,但浏览器不会立即向后端发送请求。
history的问题:前端的 URL 必须和实际向后端发起请求的 URL 一致,如www.abc.com/book/id 如果后端缺少对 /book/id 的路由处理,将返回 404 错误。Vue-Router 官网里如此描述:“不过这种模式要玩好,还需要后台配置支持……所以呢,你要在服务端增加一个覆盖所有情况的候选资源:如果 URL 匹配不到任何静态资源,则应该返回同一个 index.html 页面,这个页面就是你 app 依赖的页面。”
19.动态路由相关
20.vue-router API 参考
-
<router-link> -
- v-slot API (3.1.0 新增)
-
<router-link>Props -
- to
-
- replace
-
- append
-
- tag
-
- active-class
-
- exact
-
- event
-
- exact-active-class
-
- aria-current-value
-
<router-view>Props -
- name
-
Router 构建选项
-
- routes
-
- mode
-
- base
-
- linkActiveClass
-
- linkExactActiveClass
-
- scrollBehavior
-
- parseQuery / stringifyQuery
-
- fallback
-
Router 实例属性
-
- router.app
-
- router.mode
-
- router.currentRoute
-
- router.START_LOCATION
-
Router 实例方法
-
- router.beforeEach
-
- router.beforeResolve
-
- router.afterEach
-
- router.push
-
- router.replace
-
- router.go
-
- router.back
-
- router.forward
-
- router.getMatchedComponents
-
- router.resolve
-
- router.addRoutes
-
- router.addRoute
-
- router.addRoute
-
- router.getRoutes
-
- router.onReady
-
- router.onError
路由对象
一个路由对象 (route object) 表示当前激活的路由的状态信息,包含了当前 URL 解析得到的信息,还有 URL 匹配到的路由记录 (route records) 。
路由对象是不可变 (immutable) 的,每次成功的导航后都会产生一个新的对象。
路由对象出现在多个地方:
-
在组件内,即
this.$route -
在
$route观察者回调内 -
router.match(location)的返回值 -
导航守卫的参数:
router.beforeEach((to, from, next) => { // `to` 和 `from` 都是路由对象 }) -
scrollBehavior方法的参数:const router = new VueRouter({ scrollBehavior(to, from, savedPosition) { // `to` 和 `from` 都是路由对象 } })
路由对象属性
-
$route.path
-
类型:
string字符串,对应当前路由的路径,总是解析为绝对路径,如
"/foo/bar"。
-
-
$route.params
-
类型:
Object一个 key/value 对象,包含了动态片段和全匹配片段,如果没有路由参数,就是一个空对象。
你可以在同一个路由中设置有多个 路径参数,它们会映射到
$route.params上的相应字段。例如:
-
| 匹配模式 | 匹配路径 | $route.params |
|---|---|---|
| /users/:username | /users/eduardo | { username: 'eduardo' } |
| /users/:username/posts/:postId | /users/eduardo/posts/123 | { username: 'eduardo', postId: '123' } |
-
$route.query
-
类型:
Object一个 key/value 对象,表示 URL 查询参数。例如,对于路径
/foo?user=1,则有$route.query.user == 1,如果没有查询参数,则是个空对象。
-
-
$route.hash
-
类型:
string当前路由的 hash 值 (带
#) ,如果没有 hash 值,则为空字符串。
-
-
$route.fullPath
-
类型:
string完成解析后的 URL,包含查询参数和 hash 的完整路径。
-
-
$route.matched
- 类型:
Array<RouteRecord>
一个数组,包含当前路由的所有嵌套路径片段的路由记录 。路由记录就是
routes配置数组中的对象副本 (还有在children数组)。const router = new VueRouter({ routes: [ // 下面的对象就是路由记录 { path: '/foo', component: Foo, children: [ // 这也是个路由记录 { path: 'bar', component: Bar } ] } ] })当 URL 为
/foo/bar,$route.matched将会是一个包含从上到下的所有对象 (副本)。 - 类型:
-
$route.name
当前路由的名称,如果有的话。(查看命名路由)
-
$route.redirectedFrom
如果存在重定向,即为重定向来源的路由的名字。(参阅重定向和别名)
注入的属性
通过在 Vue 根实例的 router 配置传入 router 实例,下面这些属性成员会被注入到每个子组件。
-
this.$router
router 实例。
-
this.$route
当前激活的路由信息对象。这个属性是只读的,里面的属性是 immutable (不可变) 的,不过你可以 watch (监测变化) 它。
21.捕获所有路由或 404 Not found 路由
常规参数只匹配 url 片段之间的字符,用 / 分隔。如果我们想匹配任意路径,我们可以使用自定义的 路径参数 正则表达式,在 路径参数 后面的括号中加入 正则表达式 :
22.路由懒加载
当打包构建应用时,JavaScript 包会变得非常大,影响页面加载。如果我们能把不同路由对应的组件分割成不同的代码块,然后当路由被访问的时候才加载对应组件,这样就会更加高效。
Vue Router 支持开箱即用的动态导入,这意味着你可以用动态导入代替静态导入:
// 将
// import UserDetails from './views/UserDetails'
// 替换成
const UserDetails = () => import('./views/UserDetails')
const router = createRouter({
// ...
routes: [{ path: '/users/:id', component: UserDetails }],
})
component (和 components) 配置接收一个返回 Promise 组件的函数,Vue Router 只会在第一次进入页面时才会获取这个函数,然后使用缓存数据。这意味着你也可以使用更复杂的函数,只要它们返回一个 Promise :
const UserDetails = () =>
Promise.resolve({
/* 组件定义 */
})
一般来说,对所有的路由都使用动态导入是个好主意。
23.路由嵌套
24.路由传递参数的方式
- params 动态路由
配置路由:/router/:id
传递方式:在path后面跟对应的值
传递后形成的路径:/router/123等
- query的方式
配置路由格式:/router 即普通方式
传递:对象中使用query的key作为传递方式
传递后的路径:/router?id=123
25.路由的两种写法
to:"/router..."
:to:"{path: ..., query: {name:'xxx', age: 'ooo', ...}"
26.导航守卫
当一个导航触发时,全局前置守卫按照创建顺序调用。守卫是异步解析执行,此时导航在所有守卫 resolve 完之前一直处于 等待中。
-
to: Route: 即将要进入的目标 路由对象 -
from: Route: 当前导航正要离开的路由 -
next: Function: 一定要调用该方法来 resolve 这个钩子。执行效果依赖next方法的调用参数。next(): 进行管道中的下一个钩子。如果全部钩子执行完了,则导航的状态就是 confirmed (确认的)。next(false): 中断当前的导航。如果浏览器的 URL 改变了 (可能是用户手动或者浏览器后退按钮),那么 URL 地址会重置到from路由对应的地址。next('/')或者next({ path: '/' }): 跳转到一个不同的地址。当前的导航被中断,然后进行一个新的导航。你可以向next传递任意位置对象,且允许设置诸如replace: true、name: 'home'之类的选项以及任何用在router-link的toprop 或router.push中的选项。next(error): (2.4.0+) 如果传入next的参数是一个Error实例,则导航会被终止且该错误会被传递给router.onError()注册过的回调。
27.keep-alive
重新创建动态组件的行为通常是非常有用的,但是在这个案例中,我们更希望那些标签的组件实例能够被在它们第一次被创建的时候缓存下来。为了解决这个问题,我们可以用一个 <keep-alive> 元素将其动态组件包裹起来。