1、MVVM和MVC
- MVVM 是 Model-View-ViewModel 的缩写。
- Model:代表数据模型,也可以在Model中定义数据修改和操作的业务逻辑。
- View:代表UI 组件,它负责将数据模型转化成UI 展现出来。
- ViewModel:监听模型数据的改变和控制视图行为、处理用户交互,简单理解就是一个同步View 和 Model的对象,连接Model和View。
2、vue2的生命周期
生命周期 | 描述 |
---|---|
beforeCreate | 组件实例创建之前,还没有data属性 |
created | 组件实例创建完成,得到data可发请求赋值,但还没有生成真实dom,$el还不可用 |
beforeMount | 挂载dom之前调用 |
mounted | 完成dom挂载,el已被创建 |
beforeUpdate | 组件数据更新前调用 |
updated | 组件数据更新后调用 |
activited | keep-alive专属,组件被激活时调用 |
deactivited | keep-alive专属,组件被销毁时调用 |
beforeDestroy | 组件销毁前调用 |
destroyed | 组件销毁后调用 |
3、Vue 的父组件和子组件生命周期钩子函数执行顺序
Vue 的父组件和子组件生命周期钩子函数执行顺序可以归类为以下 4 部分:
- 加载渲染过程
父 beforeCreate -> 父 created -> 父 beforeMount -> 子 beforeCreate -> 子 created -> 子 beforeMount -> 子 mounted -> 父 mounted
- 子组件更新过程
父 beforeUpdate -> 子 beforeUpdate -> 子 updated -> 父 updated
- 父组件更新过程
父 beforeUpdate -> 父 updated
- 销毁过程
父 beforeDestroy -> 子 beforeDestroy -> 子 destroyed -> 父 destroyed
4、如何在created生命周期内获取dom
因为在created生命周期还没有挂载dom,只需要在created内进行异步操作后就可以拿到dom
// html
<div id="app"></div>
// script
created(){
axios.get('/apis/getList').then(() => {
const dom = document.getElementById('app')
console.log(dom)
})
}
5、在哪个生命周期内调用异步请求?
可以在created,beforeMount,mounted的生命周期发起异步请求,因为三个钩子函数中,data已经创建,可获取服务端数据对data数据赋值。
推荐在created生命周期中进行调用异步请求,理由如下:
- 能更快获取服务端数据,减少页面loading时间
- ssr不支持beforeMount、mounted钩子函数,所以把异步请求放在created有助于一致性
6、父组件可以监听到子组件的生命周期吗?
可以。
一、通过 $emit 实现
// 父组件
<Child @mounted="doSomething" />
// 子组件
mounted(){
this.$emit('调用子组件的mounted钩子')
}
以上方法虽然可行,但需要每次手动写一次$emit触发父组件事件。
更简单是在父组件引用子组件时通过@hook监听生命周期
二、通过 @hook 实现
// 父组件
<Child @hook:mounted="doSomething" />
doSomething(){
console.log('父组件监听到mounted钩子函数')
}
// 子组件
mounted(){
console.log('子组件触发mounted钩子函数')
}
// 以上输出顺序:
// 子组件触发mounted钩子函数
// 父组件监听到mounted钩子函数
@hook 方法不仅可以监听 mounted,也可以监听其它的生命周期事件,如:created,updated等
7、Vue 组件间通信有哪几种方式?
vue组件通信主要分3类:
1、父子通信;2、隔代通信;3、兄弟组件通信
(1) props / $emit 【适用父子通信】
(2) $ref / $parent / $children 【适用父子通信】
- $ref:如果在普通dom元素使用,引用指向dom元素,如果用到子组件上,引用就指向组件的实例
- $parent / $children:适用访问父 / 子实例
(3) eventBus($emit / $on)【适用父子、隔代、兄弟通信】
- 通过一个空的Vue实例作为中央事件中线(事件中心),用它来触发事件和监听事件,从而实现任何的组件间的通信,包括父子、隔代、兄弟组件
(4) $attrs / $listeners 【适用隔代通信】
(5) provide / inject 【适用隔代通信】
(6) Vuex 【适用 父子、隔代、兄弟组件通信】
8、vue-router 中常用的 hash 和 history 路由模式实现原理
1)hash 模式的实现原理:
基于 location.hash 来实现的。其实现原理很简单,location.hash 的值就是 URL 中 # 后面的内容。比如下面这个网站,它的 location.hash 的值为 ‘#uid’:
https://www.test.com#uid
hash 路由模式的实现主要是基于下面几个特性:
- URL 中 hash 值只是客户端的一种状态,也就是说当向服务器端发出请求时,hash 部分不会被发送;
- hash 值的改变,都会在浏览器的访问历史中增加一个记录。因此我们能通过浏览器的回退、前进按钮控制hash 的切换;
- 可以通过 a 标签,并设置 href 属性,当用户点击这个标签后,URL 的 hash 值会发生改变;或者使用 JavaScript 来对 loaction.hash 进行赋值,改变 URL 的 hash 值;
- 可以使用 hashchange 事件来监听 hash 值的变化,从而对页面进行跳转(渲染)。
(2)history 模式的实现原理:
HTML5 提供了 History API 来实现 URL 的变化。如下两个:
history.pushState()
history.repalceState()。
这两个 API 可以在不进行刷新的情况下,操作浏览器的历史纪录。
唯一不同的是,前者是新增一个历史记录,后者是直接替换当前的历史记录,如下所示:
window.history.pushState(null, null, path);
window.history.replaceState(null, null, path);
history 路由模式的实现主要基于存在下面几个特性:
- pushState 和 repalceState 两个 API 来操作实现 URL 的变化 ;
- 使用 popstate 事件来监听 url 的变化,从而对页面进行跳转(渲染);
- history.pushState() 或 history.replaceState() 不会触发 popstate 事件,这时我们需要手动触发页面跳转(渲染)。
9、Vue 项目进行优化
- v-if 和 v-show 区分使用场景
- v-for 遍历必须为 item 添加 key,且避免同时使用 v-if
- 图片资源懒加载
- 路由懒加载
- 异步组件
- 第三方插件的按需引入
- 开启 gzip 压缩
- 使用CDN
- 服务端渲染 SSR
10、为什么 data 是一个函数
- 组件中的 data 写成一个函数,数据以函数返回值形式定义,这样每复用一次组件,就会返回一份新的 data。
- 类似于给每个组件实例创建一个私有的数据空间,让各个组件实例维护各自的数据。
- 而单纯的写成对象形式,就使得所有组件实例共用了一份 data,就会造成一个变了全都会变的结果
11、理解 Vue 的单向数据流
数据总是从父组件传到子组件,子组件没有权利修改父组件传过来的数据,只能请求父组件对原始数据进行修改。这样会防止从子组件意外改变父级组件的状态,从而导致你的应用的数据流向难以理解。
- 注意:在子组件直接用 v-model 绑定父组件传过来的 prop 这样是不规范的写法 开发环境会报警告
12、v-model 原理
// 在普通标签上
<input v-model="name" /> //这一行等于下一行
<input @value="name" @input="name = $event.target.value" />
// 在组件上
<currency-input v-model="name"></currentcy-input>
/*
上行代码是下行的语法糖
<currency-input :value="name" @input="name = arguments[0]"></currency-input>
*/
// 子组件定义
Vue.component('currency-input', {
template: `
<span>
<input
ref="input"
:value="value"
@input="$emit('input', $event.target.value)"
>
</span>
`,
props: ['value'],
})
13、v-for 为什么要加 key
- 没有key的时候默认使用的是“就地复用”策略。
- 如果数据项的顺序被改变,Vue不是移动Dom元素来匹配数据项的改变,而是简单复用原来位置的每个元素。
- 如果删除第一个元素,在进行比较时发现标签一样值不一样时,就会复用之前的位置,将新值直接放到该位置,以此类推,最后多出一个就会把最后一个删除掉。
注:尽量不要使用索引值index作key值,一定要用唯一标识的值,如id等。因为若用数组索引index为key,当向数组中指定位置插入一个新元素后,因为这时候会重新更新index索引,对应着后面的虚拟DOM的key值全部更新了,这个时候还是会做不必要的更新,就像没有加key一样,因此index虽然能够解决key不冲突的问题,但是并不能解决复用的情况。如果是静态数据,用索引号index做key值是没有问题的。
14、vuex的理解
vuex 是专门为 vue 提供的全局状态管理系统,用于多个组件中数据共享、数据缓存等。(无法持久化、内部核心原理是通过创造一个全局实例 new Vue)
主要包括以下几个模块:
- State:定义了应用状态的数据结构,可以在这里设置默认的初始状态。
- Getter:允许组件从 Store 中获取数据,mapGetters 辅助函数仅仅是将 store 中的 getter 映射到局部计算属性。
- Mutation:是唯一更改 store 中状态的方法,且必须是同步函数。
- Action:用于提交 mutation,而不是直接变更状态,可以包含任意异步操作。
- Module:允许将单一的 Store 拆分为多个 store 且同时保存在单一的状态树中。
15、Vue 修饰符
(1) 事件修饰符
- .stop 阻止事件继续传播
- .prevent 阻止标签默认行为
- .capture 使用事件捕获模式,即元素自身触发的事件先在此处处理,然后才交由内部元素进行处理
- .self 只当在 event.target 是当前元素自身时触发处理函数
- .once 事件将只会触发一次
- .passive 告诉浏览器你不想阻止事件的默认行为
- .sync 修饰符
从 2.3.0 起vue重新引入了.sync修饰符,但是这次它只是作为一个编译时的语法糖存在。它会被扩展为一个自动更新父组件属性的 v-on 监听器。示例代码如下:
<component :foo.sync="bar"></component>
// 会被扩展为:
<component :foo="bar" @update:foo="val => bar = val"></component>
// 当子组件需要更新 foo 的值时,它需要显式地触发一个更新事件:
this.$emit('update:foo', newValue)
(2) v-model 的修饰符
- .lazy 通过这个修饰符,转变为在 change 事件再同步
- .number 自动将用户的输入值转化为数值类型
- .trim 自动过滤用户输入的首尾空格
(3) 键盘事件的修饰符
- .enter
- .tab
- .delete (捕获“删除”和“退格”键)
- .esc
- .space
- .up
- .down
- .left
- .right
(4) 系统修饰键
- .ctrl
- .alt
- .shift
- .meta
(5) 鼠标按钮修饰符
- .left
- .right
- .middle
15、$nextTick
vue实现响应式并不是数据发生变化后dom立即变化,而是按照一定的策略来进行dom更新。
nextTick 是在下次 DOM 更新循环结束之后执行延迟回调,在修改数据之后使用nextTick,则可以在回调中获取更新后的 DOM
16、怎么定义vue-router的动态路由?怎么获取传过来的动态参数?
在router目录下的index.js文件中,对path属性加上/:id。使用router对象的params.id获取动态参数
17、vue-router的导航钩子
常用的是router.beforeEach(to,from,next),在跳转前进行权限判断。一共有三种:
- 全局导航钩子:router.beforeEach(to,from,next)
- 组件内的钩子
- 单独路由独享组件
18、vue路由传参
(1) 使用query方法传入的参数使用this.$route.query
(2) 使用params方式传入的参数使用this.$route.params接收
19、router和route的区别
(1) route为当前router跳转对象里面可以获取name、path、query、params等
(2) router为VueRouter实例,想要导航到不同URL,则使用router.push、$router.replace方法