框架面试课-Vue使用
·
基本使用,组件使用 —— 常用,必须会
·
高级特性 —— 不常用,但体现深度
·
Vuex 和
Vue-router 使用
·
行。但这种一种最低效的方式
·
文档是一个备忘录,给会用的人查阅,并不是入门教程
·
文档全面冗长且细节过多,不能突出面试考点
·
日常使用,必须掌握,面试必考(不一定全考)
·
梳理知识点,从冗长的文档中摘出考点和重点
·
考察形式不限(参考后面的面试真题),但都在范围之内
·
指令、插值
• 插值、表达式
• 指令、动态属性
• v-html:会有 XSS 风险,会覆盖子组件
• 参考代码
• 参考代码图片
•
• 参考代码
• <template>
<div>
<p>文本插值 {{message}}</p>
<p>JS 表达式 {{ flag ? 'yes' : 'no' }} (只能是表达式,不能是 js 语句)</p>
<p :id="dynamicId">动态属性
id</p>
<hr/>
<p v-html="rawHtml">
<span>有 xss 风险</span>
<span>【注意】使用 v-html 之后,将会覆盖子元素</span>
</p>
<!-- 其他常用指令后面讲 -->
</div>
</template>
<script>
export default {
data() {
return {
message: 'hello vue',
flag: true,
rawHtml: '指令 - 原始 html
<b>加粗</b> <i>斜体</i>',
dynamicId: `id-${Date.now()}`
}
}
}
</script>
·
computed 和
watch
• computed
• computed 有缓存,data 不变则不会重新计算
• 参考代码
• 参考代码图片
•
• 参考代码
• <template>
<div>
<p>num {{num}}</p>
<p>double1 {{double1}}</p>
<input v-model="double2"/>
</div>
</template>
<script>
export default {
data() {
return {
num: 20
}
},
computed: {
double1() {
return this.num * 2
},
double2: {
get() {
return this.num * 2
},
set(val) {
this.num = val/2
}
}
}
}
</script>
• watch
• watch 如何深度监听?
• 参考代码
• 参考代码图片
•
• 参考代码
• <template>
<div>
<input v-model="name"/>
<input v-model="info.city"/>
</div>
</template>
<script>
export default {
data() {
return {
name: '双越',
info: {
city: '北京'
}
}
},
watch: {
name(oldVal, val) {
// eslint-disable-next-line
console.log('watch name', oldVal, val) // 值类型,可正常拿到 oldVal 和 val
},
info: {
handler(oldVal, val) {
// eslint-disable-next-line
console.log('watch info', oldVal, val) // 引用类型,拿不到 oldVal 。因为指针相同,此时已经指向了新的 val
},
deep: true // 深度监听
}
}
}
</script>
• watch 监听引用类型,拿不到
oldVal
·
class 和
style
• 使用动态属性
• 使用驼峰式写法
• 参考代码
• 参考代码图片
•
• 参考代码
• <template>
<div>
<p
:class="{ black: isBlack, yellow: isYellow }">使用
class</p>
<p :class="[black, yellow]">使用
class (数组)</p>
<p :style="styleData">使用
style</p>
</div>
</template>
<script>
export default {
data() {
return {
isBlack: true,
isYellow: true,
black: 'black',
yellow: 'yellow',
styleData: {
fontSize: '40px', // 转换为驼峰式
color: 'red',
backgroundColor: '#ccc' // 转换为驼峰式
}
}
}
}
</script>
<style scoped>
.black {
background-color: #999;
}
.yellow {
color: yellow;
}
</style>
·
条件渲染
• v-if v-else 的用法,可使用变量,也可以使用 === 表达式
• v-if 和 v-show 的区别?
• v-show只编译一次,后面其实就是控制css,而v-if不停的销毁和创建,故v-show性能更好一点。
• v-if 和 v-show 的使用场景?
• v-if 与 v-show 都能控制dom元素在页面的显示
• v-if 相比 v-show 开销更大的(直接操作dom节点增加与删除)
• 如果在运行时条件很少改变,则使用 v-if 较好
• 如果需要非常频繁地切换,则使用 v-show 较好
• 参考代码
• 参考代码图片
•
• 参考代码
• <template>
<div>
<p v-if="type === 'a'">A</p>
<p v-else-if="type === 'b'">B</p>
<p v-else>other</p>
<p v-show="type === 'a'">A by v-show</p>
<p v-show="type === 'b'">B by v-show</p>
</div>
</template>
<script>
export default {
data() {
return {
type: 'a'
}
}
}
</script>
·
循环(列表)渲染
• 如何遍历对象? ——
也可以用 v-for
• key 的重要性。key 不能乱写(如 random 或者 index)
• v-for 和 v-if 不能一起使用!
• v-for优先级比v-if高
• 参考代码
• 参考代码图片
•
• 参考代码
• <template>
<div>
<p>遍历数组</p>
<ul>
<li v-for="(item, index) in listArr"
:key="item.id">
{{index}} - {{item.id}} - {{item.title}}
</li>
</ul>
<p>遍历对象</p>
<ul >
<li v-for="(val, key, index) in listObj"
:key="key">
{{index}} - {{key}} -
{{val.title}}
</li>
</ul>
</div>
</template>
<script>
export default {
data() {
return {
flag: false,
listArr: [
{ id: 'a',
title: '标题1' }, // 数据结构中,最好有 id
,方便使用 key
{ id: 'b', title: '标题2' },
{ id: 'c', title: '标题3' }
],
listObj: {
a: { title: '标题1' },
b: { title: '标题2' },
c:
{ title: '标题3' },
}
}
}
}
</script>
·
事件
• event 参数,自定义参数
• 【观察】事件被绑定到哪里?
• 1. event 是原生的
• 2. 事件被挂载到当前元素
• 参考代码
• 参考代码图片
•
•
• 参考代码
• <template>
<div>
<p>{{num}}</p>
<button @click="increment1">+1</button>
<button @click="increment2(2, $event)">+2</button>
</div>
</template>
<script>
export default {
data() {
return {
num: 0
}
},
methods: {
increment1(event) {
// eslint-disable-next-line
console.log('event', event, event.__proto__.constructor) // 是原生的 event
对象
// eslint-disable-next-line
console.log(event.target)
// eslint-disable-next-line
console.log(event.currentTarget) // 注意,事件是被注册到当前元素的,和 React 不一样
this.num++
// 1. event 是原生的
// 2. 事件被挂载到当前元素
// 和 DOM 事件一样
},
increment2(val, event) {
// eslint-disable-next-line
console.log(event.target)
this.num = this.num + val
},
loadHandler() {
// do some thing
}
},
mounted() {
window.addEventListener('load', this.loadHandler)
},
beforeDestroy() {
//【注意】用 vue 绑定的事件,组建销毁时会自动被解绑
// 自己绑定的事件,需要自己销毁!!!
window.removeEventListener('load', this.loadHandler)
}
}
</script>
·
事件修饰符
• 参考代码图片
•
·
按键修饰符
• 参考代码图片
•
·
表单
• v-model
• 常见表单项
textarea checkbox radio select
• 修饰符 lazy
number trim
• 参考代码
• 参考代码图片
•
•
• 参考代码
• <template>
<div>
<p>输入框: {{name}}</p>
<input type="text" v-model.trim="name"/>
<input type="text" v-model.lazy="name"/> <!-- 防抖
-->
<input type="text" v-model.number="age"/>
<p>多行文本: {{desc}}</p>
<textarea v-model="desc"></textarea>
<!-- 注意,<textarea>{{desc}}</textarea>
是不允许的!!! -->
<p>复选框 {{checked}}</p>
<input type="checkbox" v-model="checked"/>
<p>多个复选框 {{checkedNames}}</p>
<input type="checkbox" id="jack"
value="Jack" v-model="checkedNames">
<label for="jack">Jack</label>
<input type="checkbox" id="john"
value="John" v-model="checkedNames">
<label for="john">John</label>
<input type="checkbox" id="mike"
value="Mike" v-model="checkedNames">
<label for="mike">Mike</label>
<p>单选 {{gender}}</p>
<input type="radio" id="male"
value="male" v-model="gender"/>
<label for="male">男</label>
<input type="radio" id="female"
value="female" v-model="gender"/>
<label for="female">女</label>
<p>下拉列表选择 {{selected}}</p>
<select v-model="selected">
<option disabled value="">请选择</option>
<option>A</option>
<option>B</option>
<option>C</option>
</select>
<p>下拉列表选择(多选) {{selectedList}}</p>
<select v-model="selectedList" multiple>
<option disabled value="">请选择</option>
<option>A</option>
<option>B</option>
<option>C</option>
</select>
</div>
</template>
<script>
export default {
data() {
return {
name: '双越',
age: 18,
desc: '自我介绍',
checked: true,
checkedNames: [],
gender: 'male',
selected: '',
selectedList: []
}
}
}
</script>
·
必须掌握,否则面试不会通过
·
重点和考点
·
props
• 父组件向子组件传递一个信息
·
$emit
• 子组件调用父组件的事件
·
组件间通讯 - 自定义事件
• 绑定自定义事件
• 参考代码图片
•
• 触发自定义事件
• 参考代码图片
•
•
• 及时销毁,否则可能造成内存泄露
• 参考代码图片
•
• 注意事项:调用父组件的事件和调用自定义事件是不一样的
·
组件生命周期
• 挂载阶段
• 参考代码图片
•
• index为父组件
• list为子组件
• 更新阶段
• 参考代码图片
•
• index为父组件
• list为子组件
• 销毁阶段
• 删除子组件触发生命周期顺序:
• 父beforeUpdate
=> 子beforeDestroy => 子destroyed
=> 父updated
·
生命周期(父子组件)
• 在生命周期中,created表示初始化完,mounted,表示渲染完
• vue的生命周期函数参考图片
•
• 父组件初始化完,再初始化子组件,子组件渲染完,再渲染父组件
• 参考代码图片
•
•
·
子组件
• input.vue
• <template>
<div>
<input type="text" v-model="title"/>
<button @click="addTitle">add</button>
</div>
</template>
<script>
import event from './event'
export default {
data() {
return {
title: ''
}
},
methods: {
addTitle() {
// 调用父组件的事件
this.$emit('add', this.title)
// 调用自定义事件
event.$emit('onAddTitle', this.title)
this.title = ''
}
}
}
</script>
• List.vue
• <template>
<div>
<ul>
<li v-for="item in list" :key="item.id">
{{item.title}}
<button @click="deleteItem(item.id)">删除</button>
</li>
</ul>
</div>
</template>
<script>
import event from './event'
export default {
// props: ['list']
props: {
// prop 类型和默认值
list: {
type: Array,
default() {
return []
}
}
},
data() {
return {
}
},
methods: {
deleteItem(id) {
this.$emit('delete', id)
},
addTitleHandler(title) {
// eslint-disable-next-line
console.log('on add title', title)
}
},
created() {
// eslint-disable-next-line
console.log('list created')
},
mounted() {
// eslint-disable-next-line
console.log('list mounted')
// 绑定自定义事件
event.$on('onAddTitle', this.addTitleHandler)
},
beforeUpdate() {
// eslint-disable-next-line
console.log('list before update')
},
updated() {
// eslint-disable-next-line
console.log('list updated')
},
beforeDestroy() {
// 及时销毁,否则可能造成内存泄露
event.$off('onAddTitle', this.addTitleHandler)
}
}
</script>
• event.js
• import Vue from
'vue'
export default new Vue()
·
父组件
• index.vue
• <template>
<div>
<Input @add="addHandler"/>
<List :list="list" @delete="deleteHandler"/>
</div>
</template>
<script>
import Input from './Input'
import List from './List'
export default {
components: {
Input,
List
},
data() {
return {
list: [
{
id: 'id-1',
title: '标题1'
},
{
id: 'id-2',
title: '标题2'
}
]
}
},
methods: {
addHandler(title) {
this.list.push({
id: `id-${Date.now()}`,
title
})
},
deleteHandler(id) {
this.list = this.list.filter(item => item.id !== id)
}
},
created() {
// eslint-disable-next-line
console.log('index created')
},
mounted() {
// eslint-disable-next-line
console.log('index mounted')
},
beforeUpdate() {
// eslint-disable-next-line
console.log('index before update')
},
updated() {
// eslint-disable-next-line
console.log('index updated')
},
}
</script>
·
面试
• 不是每个都很常用,但用到的时候必须要知道
• 考察候选人对 Vue
的掌握是否全面,且有深度
• 考察做过的项目是否有深度和复杂度(至少能用到高级特性)
·
内容
• 自定义
v-model
• $nextTick
• ref
• slot
• 动态、异步组件
• keep-alive
• 缓存的
• mixin
• 重复逻辑
·
·
·
参考代码
• <template>
<!-- 例如:vue 颜色选择 -->
<!-- 可理解为当输入的时候调用事件change1,并把事件的target.value传递回去 -->
<input
type="text"
:value="text1"
@input="$emit('change1', $event.target.value)"
>
<!--
1. 上面的 input 使用了 :value 而不是 v-model
2. 上面的 change1 和
model.event1 要对应起来
3. text1 属性对应起来
-->
</template>
<script>
export default {
model: {
prop: 'text1', // 对应 props text1
event: 'change1'
},
// 接受v-model传递过来的属性
props: {
text1: String,
default() {
return ''
}
}
}
</script>
·
Vue 是异步渲染(原理部分会详细讲解)
·
data 改变之后,DOM 不会立刻渲染
·
$nextTick 会在
DOM 渲染之后被触发,以获取最新 DOM 节点
·
参考代码
• 参考代码图片
•
• 参考代码
• <template>
<div id="app">
<ul
ref="ul1">
<li v-for="(item, index) in list"
:key="index">
{{item}}
</li>
</ul>
<button
@click="addItem">添加一项</button>
</div>
</template>
<script>
export default {
name: 'app',
data() {
return {
list: ['a', 'b', 'c']
}
},
methods: {
addItem() {
// 这里是往列表中加入时间戳
this.list.push(`${Date.now()}`)
this.list.push(`${Date.now()}`)
this.list.push(`${Date.now()}`)
// 1. 异步渲染,$nextTick 待
DOM 渲染完再回调
// 2. 页面渲染时会将 data 的修改做整合,多次 data 修改只会渲染一次
// 3. $nextTick 会在 DOM 渲染之后被触发,以获取最新 DOM 节点
this.$nextTick(() => {
// 获取 DOM 元素
const ulElem = this.$refs.ul1
// eslint-disable-next-line
// $nextTick 会在 DOM 渲染之后被触发,以获取最新 DOM 节点
console.log( ulElem.childNodes.length )
})
}
}
}
</script>
·
vue中拿到节点的元素
• 参考代码图片
•
·
参考代码图片
• index.vue
•
•
• SlotDemo.vue
•
• 参考代码
• <template>
<a
:href="url">
<slot>
默认内容,即父组件没设置内容时,这里显示
</slot>
</a>
</template>
<script>
export default {
props: ['url'],
data() {
return {}
}
}
</script>
·
slot的意义
• 父组件往子组件中插入内容
·
参考代码图片
•
• 参考代码
• <template>
<a
:href="url">
<!-- 把子组件本地的data值传给父组件 -->
<!-- 1.绑定并自定义一个名slotData,然后对接子组件的data值对象website -->
<slot :slotData="website">
{{website.subTitle}} <!-- 默认值显示 subTitle ,即父组件不传内容时 -->
</slot>
</a>
</template>
<script>
export default {
props: ['url'],
data() {
return {
website: {
url:
'http://wangEditor.com/',
title: 'wangEditor',
subTitle: '轻量级富文本编辑器'
}
}
}
}
</script>
•
·
参考代码图片
•
•
·
就是有名字的插槽
·
组件的变化,根据数据的变化来变化
·
·
·
·
缓存组件
• 一般情况下,v-if切换的时候会销毁原有的,重新渲染新的,但是用keep-alive包裹起来之后,就可以不用重复渲染了。有点类似v-show,但是v-show是通过样式来控制的。
• 频繁切换组件,不需要重复渲染
• Vue常见性能优化方法之一
·
参考代码图片
•
•
• 参考代码
• <template>
<div>
<button @click="changeState('A')">A</button>
<button @click="changeState('B')">B</button>
<button @click="changeState('C')">C</button>
<!--一般情况下,v-if切换的时候会销毁原有的,重新渲染新的。 v-show切换的时候修改样式,display:none -->
<!-- tab 切换 -->
<!-- keep-alive 包裹起来之后,就会 -->
<keep-alive>
<KeepAliveStageA v-if="state === 'A'"/>
<KeepAliveStageB v-if="state === 'B'"/>
<KeepAliveStageC v-if="state === 'C'"/>
</keep-alive>
</div>
</template>
<script>
import KeepAliveStageA from './KeepAliveStateA'
import KeepAliveStageB from './KeepAliveStateB'
import KeepAliveStageC from './KeepAliveStateC'
export default {
components: {
KeepAliveStageA,
KeepAliveStageB,
KeepAliveStageC
},
data() {
return {
state: 'A'
}
},
methods: {
changeState(state) {
this.state = state
}
}
}
</script>
·
多个组件有相同的逻辑,抽离出来
·
mixin并不是完美的解决方案,会有一些问题
• 变量来源不明确,不利于阅读
• 多个mixin可能会造成命名冲突
• 写完不一定会报错,还有可能覆盖了
• mixin和组件可能出现多对多的关系,复杂度较高
·
Vue3提出的composition
API旨在解决这些问题
·
参考代码图片
•
•
• 参考代码
• mixin.js
• export default {
data() {
return {
city: '北京'
}
},
methods: {
showName() {
// eslint-disable-next-line
console.log(this.name)
}
},
mounted() {
// eslint-disable-next-line
console.log('mixin mounted', this.name)
}
}
• MixinDemo.vue
• <template>
<div>
<p>{{name}} {{major}} {{city}}</p>
<button @click="showName">显示姓名</button>
</div>
</template>
<script>
import myMixin from './mixin'
export default {
mixins: [myMixin], // 可以添加多个,会自动合并起来
data() {
return {
name: '双越',
major: 'web 前端'
}
},
methods: {
},
mounted() {
// eslint-disable-next-line
console.log('component mounted', this.name)
}
}
</script>
·
自定义 v-model
·
$nextTick
·
refs
·
slot
·
动态、异步组件
·
keep-alive
• 缓存的
·
mixin
• 重复逻辑
·
可以不太深入,但必须知道
·
熟悉基本用法,了解使用场景
·
最好能和自己的项目经验结合起来
·
面试考点并不多(因为熟悉 Vue 之后,vuex 没有难度)
·
但基本概念、基本使用和 API 必须要掌握
·
可能会考察 state 的数据结构设计(后面会讲)
·
state
·
getters
·
action
·
mutation
·
dispatch
·
commit
·
mapState
·
mapGetters
·
mapActions
·
mapMutations
·
·
面试考点并不多(前提是熟悉 Vue)
·
路由模式(hash、 H5 history)
·
路由配置(动态路由、懒加载)
·
hash 模式(默认),如 http://abc.com/#/user/10
·
H5 history 模式,如
http://abc.com/user/20
• 需要 server 端支持
• 使用h5
history模式
• 参考代码图片
•
·
参考代码图片
•
·
参考代码图片
•
·
面试考点并不多(前提是熟悉 Vue)
·
掌握基本概念,基本使用
·
面试官时间有限,需考察最核心、最常用的问题,而非边角问题
·
基本使用,组件使用
·
高级特性
·
Vuex 和
Vue-router 使用
·
v-show 和
v-if 的区别
• v-show是通过样式的display:none去控制的。v-if是根据vue本身的机制去控制,需要时才渲染。组件频繁切换的时候建议使用v-show,组件切换一次不频繁切换的时候用v-if
·
为何 v-for 中要用 key
• 原理中有解释
·
描述 Vue 组件生命周期(有父子组件的情况)
·
Vue 组件如何通讯
• 属性,触发事件
• 父子组件,可以考虑
• 自定义事件
• 组件和组件之间没有关系,可以考虑
• vuex
• 组件和组件之间没有关系,可以考虑
·
描述组件渲染和更新的过程
• 原理中有解释
·
双向数据绑定 v-model 的实现原理
• 原理中有解释
·
面试为何会考察原理?
·
面试中如何考察?以何种方式?
·
Vue 原理包括哪些?
·
知其然而知其所以然 —— 各行业通用的道理
·
了解原理,才能应用的更好(竞争激烈,择优录取)
·
大厂造轮子(有钱有资源,业务定制,技术 KPI)
·
考察重点,而不是考察细节。掌握好 2/8 原则
·
和使用相关联的原理,例如 vdom、模板渲染
·
整体流程是否全面?热门技术是否有深度?
·
组件化
·
响应式
·
vdom 和
diff
·
模板编译
·
渲染过程
·
前端路由
·
“很久以前”就有组件化
• asp jsp php 已经有组件化了
• nodejs 中也有类似的组件化
·
数据驱动视图(MVVM,setState)
• 传统组件,只是静态渲染,更新还要依赖于操作 DOM
• 根据数据来驱动视图
• 数据驱动视图 –
Vue MVVM
• 数据驱动视图 –
React setState(暂时按下不表)
• 参考图片
•
·
组件 data 的数据一旦变化,立刻触发视图的更新
·
实现数据驱动视图的第一步
·
考察 Vue 原理的第一题
·
核心 API - Object.defineProperty
• 3.0之前用的这个
·
Object.defineProperty 的一些缺点(Vue3.0 启用 Proxy)
·
Proxy 兼容性不好,且无法 polyfill
·
Vue2.x 还会存在一段时间,所以都得学
·
Vue3.0 相关知识,下一章讲,这里只是先提一下
·
参考代码图片
•
•
·
监听对象,监听数组
·
复杂对象,深度监听
·
几个缺点
·
深度监听,需要递归到底,一次性计算量大
·
无法监听新增属性/删除属性
• 所以使用Vue.set
Vue.delete来代替
·
无法原生监听数组,需要特殊处理
·
基础 API – Object.defineProperty
·
如何监听对象(深度监听),监听数组
·
Object.defineProperty 的缺点
·
vdom 是实现
vue 和 React 的重要基石
·
diff 算法是
vdom 中最核心、最关键的部分
·
vdom 是一个热门话题,也是面试中的热门问题
·
DOM 操作非常耗费性能
·
以前用 jQuery ,可以自行控制 DOM 操作的时机,手动调整
·
Vue 和
React 是数据驱动视图,如何有效控制 DOM 操作?
·
有了一定复杂度,想减少计算次数比较难
·
能不能把计算,更多的转移为 JS 计算?因为 JS 执行速度很快
·
vdom - 用 JS
模拟 DOM 结构,计算出最小的变更,操作 DOM
·
参考代码图片
•
•
• 标签
• 属性
• 子元素
·
简洁强大的 vdom 库,易学易用
·
Vue 参考它实现的 vdom 和 diff
·
https://github.com/snabbdom/snabbdom
·
Vue3.0 重写了
vdom 的代码,优化了性能
·
但 vdom 的基本理念不变,面试考点也不变
·
React vdom 具体实现和 Vue 也不同,但不妨碍统一学习
·
snabbdom 重点总结
• h 函数
• vnode 数据结构
• patch 函数
·
vdom 总结
• 用 JS 模拟 DOM 结构(vnode)
• 新旧 vnode 对比,得出最小的更新范围,最后更新 DOM
• 数据驱动视图的模式下,有效控制 DOM 操作
·
问题
• 新旧 vnode 是如何比较的?
• 传说中的 diff 算法是什么?
• patch 函数内部是如何实现的?
·
diff 算法是
vdom 中最核心、最关键的部分
·
diff 算法能在日常使用 vue React 中体现出来(如 key)
·
diff 算法是前端热门话题,面试“宠儿”
·
diff 即对比,是一个广泛的概念,如 linux diff 命令、git diff 等
·
两个 js 对象也可以做 diff ,如
https://github.com/cujojs/jiff
·
两棵树做 diff ,如这里的 vdom dif
·
参考图片
•
·
第一,遍历 tree1
·
第二,遍历 tree2
·
第三,排序
·
1000 个节点,要计算 1 亿次,算法不可用
·
只比较同一层级,不跨级比较
• 参考图片
•
·
tag 不相同,则直接删掉重建,不再深度比较
• 参考图片
•
·
tag 和
key ,两者都相同,则认为是相同节点,不再深度比较