考察点
框架的使用(基本使用,高级特性,周边插件)
框架的原理(基本原理的了解,热门技术的深度,全面性)
框架的实际应用,即设计能力(组件结构、数据结构)
面试技巧
荣耀黄金
生命周期
beforeDestroy处理
及时销毁全局监听事件,避免内存泄露
如even.$off(‘xxx’)
父子组件生命周期顺序
刚开始先创建父,再创建子,后需要保证子组建渲染完,再到父组件
父beforeCreate -> 父created -> 父beforeMount -> 子beforeCreate -> 子created -> 子beforeMount -> 子mounted -> 父mounted
更新数据涉及同变量
父beforeUpdate-> 子beforeUpdate->子updated->父updated
$nextTick
Vue的Dom渲染过程是异步执行的
data改变之后,Dom不会立即渲染
$nextTick会在Dom渲染之后被触发,以获取最新Dom节点
正是因为Vue是异步更新Dom,所以当我们修改数据之后,Dom节点的内容不会立即修改,我们这样获取Dom节点的新内容的时候,获取的还是旧的内容
nextTick 是 Vue.js 提供的一个函数,并非浏览器内置。nextTick 函数接收一个回调函数 cb
,在下一个 DOM 更新循环之后执行。比如下面的示例:
<template>
<div>
<p v-if="show" ref="node">内容</p>
<button @click="handleShow">显示</button>
</div>
</template>
<script>
export default {
data () {
return {
show: false
}
},
methods: {
handleShow () {
this.show = true;
console.log(this.$refs.node); // undefined
this.$nextTick(() => {
console.log(this.$refs.node); // <p>内容</p>
});
}
}
}
</script>
当 show
被置为 true 时,这时 p 节点还未被渲染,因此打印出的是 undefined,而在 nextTick 的回调里,p 已经渲染好了,这时能正确打印出节点。
nextTick 的源码在 github.com/vuejs/vue/b…,可以看到,Vue.js 使用了 Promise
、setTimeout
和 setImmediate
三种方法来实现 nextTick,在不同环境会使用不同的方法。
v-show 与 v-if 区别及使用场景
(条件渲染)v-show
只是 CSS 级别的 display: none;
和 display: block;
之间的切换,而 v-if
决定是否会选择代码块的内容(或组件)。
进一步,选型上:什么时候用 v-show,什么时候用 v-if ?
使用场景:
频繁操作时,使用 v-show
,一次性渲染完的,使用 v-if
那使用 v-if
在性能优化上有什么经验?
因为当 v-if="false"
时,内部组件是不会渲染的,所以在特定条件才渲染部分组件(或内容)时,可以先将条件设置为 false
,需要时(或异步,比如 $nextTick)再设置为 true
,这样可以优先渲染重要的其它内容,合理利用,可以进行性能优化。
绑定 class 的数组用法
动态绑定 class 应该不陌生吧,这也是最基本的,但是这个问题却有点绕,什么叫绑定 class 的数组用法?我们看一下,最常用的绑定 class 怎么写:
<template>
<div :class="{show: isShow}">内容</div>
</template>
<script>
export default {
data () {
return {
isShow: true
}
}
}
</script>
绑定 class 的对象用法能满足大部分业务需求,不过,在复杂的场景下,会用到数组,来看示例:
<template>
<div :class="classes"></div>
</template>
<script>
export default {
computed: {
classes () {
return [
`${prefixCls}`,
`${prefixCls}-${this.type}`,
{
[`${prefixCls}-long`]: this.long,
[`${prefixCls}-${this.shape}`]: !!this.shape,
[`${prefixCls}-${this.size}`]: this.size !== 'default',
[`${prefixCls}-loading`]: this.loading != null && this.loading,
[`${prefixCls}-icon-only`]: !this.showSlot && (!!this.icon || !!this.customIcon || this.loading),
[`${prefixCls}-ghost`]: this.ghost
}
];
}
}
}
</script>
示例来自 iView 的 Button 组件,可以看到,数组里,可以是固定的值,还有动态值(对象)的混合。
计算属性和 watch 的区别
这里主要需要回答选型上的区别,而不是api的区别。
这个问题会延伸出几个问题:
- computed 是一个对象时,它有哪些选项?
- computed 和 methods 有什么区别?
- computed 是否能依赖其它组件的数据?
- watch 是一个对象时,它有哪些选项?
问题 1,已经在 16 小节介绍过,有 get 和 set 两个选项。
问题 2,methods 是一个方法,它可以接受参数,而 computed 不能;computed 是可以缓存的,methods 不会;一般在 v-for
里,需要根据当前项动态绑定值时,只能用 methods 而不能用 computed,因为 computed 不能传参。
问题 3,computed 可以依赖其它 computed,甚至是其它组件的 data。
问题 4,第 16 小节也有提到,有以下常用的配置:
- handler 执行的函数
- deep 是否深度
- immediate 是否立即执行
《1》计算属性computed 必须要返回一个值哦 通过return来返回的
会缓存,只要数据不发生变化,就使用缓存的数据
《2》 watch的回调函数里面有两个参数,分别是newval和oldval。
不会缓存 只要数据发生变化 就会重新的去计算
另外,需要额外说明的,计算属性可以实现一个值是根据多个值的动态计算结果,而watch偏向与根据一个值得出多个依赖结果,所以watch能做,计算属性不一定能做,但计算属性能做的可以理解为watch都能做,但实现起来会比较麻烦。
事件修饰符
这个问题我会先写一段代码:
<custom-component>内容</custom-component>
然后问:怎样给这个自定义组件 custom-component 绑定一个**原生**
的 click 事件?
我一开始并不会问什么是事件修饰符,但是如果候选人说 <custom-component @click="xxx">
,就已经错了,说明它对这个没有概念。这里的 @click
是自定义事件 click,并不是原生事件 click。绑定原生的 click 是这样的:
1,给vue组件绑定事件时候,必须加上native ,否则会认为监听的是来自Item组件自定义的事件
2,等同于在子组件中: 子组件内部处理click事件然后向外发送click事件:$emit("click".fn)
<custom-component @click.native="xxx">内容</custom-component>
该问题会引申很多,比如常见的事件修饰符有哪些?如果你能说上 .exact
,说明你是个很爱探索的人,会大大加分哦。
.exact 是 Vue.js 2.5.0 新加的,它允许你控制由精确的系统修饰符组合触发的事件,比如:
<!-- 即使 Alt 或 Shift 被一同按下时也会触发 -->
<button @click.ctrl="onClick">A</button>
<!-- 有且只有 Ctrl 被按下的时候才触发 -->
<button @click.ctrl.exact="onCtrlClick">A</button>
<!-- 没有任何系统修饰符被按下的时候才触发 -->
<button @click.exact="onClick">A</button>
你可能还需要了解常用的几个事件修饰符:
.stop 防止冒泡
.prevent
.capture
.self
passive就是为了告诉浏览器,不用查询了,我们没用preventDefault阻止默认动作。passive与prevent互斥
而且,事件修饰符在连用时,是有先后顺序的。
组件中 data 为什么是函数
为什么组件中的 data 必须是一个函数,然后 return 一个对象,而 new Vue 实例里,data 可以直接是一个对象?
因为组件是用来复用的,JS 里对象是引用关系,若是对象会共享一个数据这样作用域没有隔离,而 new Vue 的实例,是不会被复用的,因此不存在引用对象的问题。
详解:组件被定义,data 必须声明为返回一个初始数据对象的函数,因为组件可能被用来创建多个实例。如果 data 仍然是一个纯粹的对象,则所有的实例将共享引用同一个数据对象!通过提供 data 函数,每次创建一个新实例后,我们能够调用 data 函数,从而返回初始数据的一个全新副本数据对象。(也就是说写成函数,会有函数作用域的概念 ,是私有函数,只作用到当前组件中)
参考:https://blog.csdn.net/weixin_42707181/article/details/99638879
递归组件的要求
回答这道题,首先你得知道什么是递归组件。而不到 10% 的人知道递归组件。其实在实际业务中用的确实不多,在独立组件中会经常使用,那回到问题,递归组件的要求是什么?主要有两个:
- 要给组件设置 name;
- 要有一个明确的结束条件。
应用场景:如菜单递归
Vuex 中 mutations 和 actions 的区别
主要的区别是,actions 可以执行异步。actions 是调用 mutations,而 mutations 来修改 store。
Render 函数
这是比较难的一题了,因为很少有人会去了解 Vue.js 的 Render 函数,因为基本用不到。遇到这个问题,一般可以从这几个方面来回答:
- 什么是 Render 函数,它的使用场景是什么。
- createElement 是什么?
- Render 函数有哪些常用的参数?
说到 Render 函数,就要说到虚拟 DOM(Virtual DOM),Virtual DOM 并不是真正意义上的 DOM,而是一个轻量级的 JavaScript 对象,在状态发生变化时,Virtual DOM 会进行 Diff 运算,来更新只需要被替换的 DOM,而不是全部重绘。
它的使用场景,就是完全发挥 JavaScript 的编程能力,有时需要结合 JSX 来使用。
createElement 是 Render 函数的核心,它构成了 Vue Virtual DOM 的模板,它有 3 个参数:
createElement () {
// {String | Object | Function}
// 一个 HTML 标签,组件选项,或一个函数
// 必须 return 上述其中一个
'div',
// {Object}
// 一个对应属性的数据对象,可选
// 您可以在 template 中使用
{
// 详细的属性
},
// {String | Array}
// 子节点(VNodes),可选
[
createElement('h1', 'hello world'),
createElement(MyComponent, {
props: {
someProps: 'foo'
}
}),
'bar'
]
}
常用的参数,主要是指上面第二个参数里的值了,这个比较多,得去看 Vue.js 的文档。
怎样理解单向数据流
这个概念出现在组件通信。父组件是通过 prop 把数据传递到子组件的,但是这个 prop 只能由父组件修改,子组件不能修改,否则会报错。子组件想修改时,只能通过 $emit
派发一个自定义事件,父组件接收到后,由父组件修改。
扩展vuex机制就是单项数据流
一般来说,对于子组件想要更改父组件状态的场景,可以有两种方案:
- 在子组件的 data 中拷贝一份 prop,data 是可以修改的,但 prop 不能:
export default {
props: {
value: String
},
data () {
return {
currentValue: this.value
}
}
}
- 如果是对 prop 值的转换,可以使用计算属性:
export default {
props: ['size'],
computed: {
normalizedSize: function () {
return this.size.trim().toLowerCase();
}
}
}
如果你能提到 v-model 实现数据的双向绑定、.sync 用法,会大大加分的,这些在第 16 节已经详细介绍过。
生命周期
Vue.js 生命周期 主要有 8 个阶段:
- 创建前 / 后(beforeCreate / created):在 beforeCreate 阶段,Vue 实例的挂载元素 el 和数据对象 data 都为 undefined,还未初始化。在 created 阶段,Vue 实例的数据对象 data 有了,el 还没有。
- 载入前 / 后(beforeMount / mounted):在 beforeMount 阶段,Vue 实例的 $el 和 data 都初始化了,但还是挂载之前为虚拟的 DOM 节点,data 尚未替换。在 mounted 阶段,Vue 实例挂载完成,data 成功渲染。
- 更新前 / 后(beforeUpdate / updated):当 data 变化时,会触发 beforeUpdate 和 updated 方法。这两个不常用,且不推荐使用。
- 销毁前 / 后(beforeDestroy / destroyed):beforeDestroy 是在 Vue 实例销毁前触发,一般在这里要通过 removeEventListener 解除手动绑定的事件。实例销毁后,触发 destroyed。
组件间通信
这个问题看似简单,却比较大,回答时,可以拆分为几种场景:
- 父子通信:
父向子传递数据是通过 props,子向父是通过 events($emit);通过父链 / 子链也可以通信($parent / $children);ref
也可以访问组件实例;provide / inject API。 - 兄弟通信:
Bus;Vuex; - 跨级通信:
Bus;Vuex;provide / inject API。
除了常规的通信方法,本册介绍的 dispatch / broadcast 和 findComponents 系列方法也可以说的,如果能说到这些,说明你对 Vue.js 组件已经有较深入的研究。
路由的跳转方式
一般有两种:
- 通过
<router-link to="home">
,router-link 标签会渲染为<a>
标签,在 template 中的跳转都是用这种; - 另一种是编程式导航,也就是通过 JS 跳转,比如
router.push('/home')
。
Vue.js 2.x 双向绑定原理
这个问题几乎是面试必问的,回答也是有深有浅。基本上要知道核心的 API 是通Object.defineProperty()
来劫持各个属性的 setter / getter,在数据变动时发布消息给订阅者,触发相应的监听回调,这也是为什么 Vue.js 2.x 不支持 IE8 的原因(IE 8 不支持此 API,且无法通过 polyfill 实现)。
vue高级用法
自定义组件v-model
$nextTick
永恒钻石
什么是 MVVM,与 MVC 有什么区别
MVVM 模式是由经典的软件架构 MVC 衍生来的。当 View(视图层)变化时,会自动更新到 ViewModel(视图模型),反之亦然。View 和 ViewModel 之间通过双向绑定(data-binding)建立联系。与 MVC 不同的是,它没有 Controller 层,而是演变为 ViewModel。
ViewModel 通过双向数据绑定把 View 层和 Model 层连接了起来,而 View 和 Model 之间的同步工作是由 Vue.js 完成的,我们不需要手动操作 DOM,只需要维护好数据状态。
父组件和子组件之间的生命周期执行顺序
为什么v-if和v-for不建议用在同一标签?
在Vue2中,v-for优先级是高于v-if的,咱们来看例子
<div v-for="item in [1, 2, 3, 4, 5, 6, 7]" v-if="item !== 3">
{{item}}
</div>
上面的写法是v-for和v-if同时存在,会先把7个元素都遍历出来,然后再一个个判断是否为3,并把3给隐藏掉,这样的坏处就是,渲染了无用的3节点,增加无用的dom操作,建议使用computed来解决这个问题:
<div v-for="item in list">
{{item}}
</div>
computed() {
list() {
return [1, 2, 3, 4, 5, 6, 7].filter(item => item !== 3)
}
}
为何在v-for中用key
- 原则是,尽量使key为唯一值,index作为下标也会存在一些问题,比如数组的增删
- diff算法中通过tag和key来判断,是否是相同的node
- 减少渲染次数,提升渲染性能
computed和watch有何区别?
- 1.computed是依赖已有的变量来计算一个目标变量,大多数情况都是多个变量凑在一起计算出一个变量,并且computed具有缓存机制,依赖值不变的情况下其会直接读取缓存进行复用,computed不能进行异步操作(需要立即return返回同步目标值数据)
- 2.watch是监听某一个变量的变化,并执行相应的回调函数,通常是一个变量的变化决定多个变量的变化,watch可以进行异步操作
- 3.简单记就是:一般情况下computed是多对一,watch是一对多
vue computed的缓存特性
概述:computed的计算属性有缓存机制,只有当其依赖的响应式数据发生变化时才会清空缓存重新计算结果
其缓存机制本质是通过一个dirty属性控制的,只有dirty为true时才会重新计算结果替换缓存。 dirty只有当其响应式数据发送变化时才会设置为true,重新计算后会再次被设置为false
[
](https://blog.csdn.net/SJ1551/article/details/109804232)
vuex的有哪些属性?用处是什么?
- State:定义了应用状态的数据结构,可以在这里设置默认的初始状态。
- Getter:允许组件从 Store 中获取数据,mapGetters 辅助函数仅仅是将 store 中的 getter 映射到局部计算属性。
- Mutation:是唯一更改 store 中状态的方法,且必须是同步函数。
- Action:用于提交 mutation,而不是直接变更状态,可以包含任意异步操作。
- Module:允许将单一的 Store 拆分为多个 store 且同时保存在单一的状态树中。
如何用JS实现hash路由
window.onhashchange
如何用JS实现H5 history路由
hash和history如何选择
至尊星耀
不需要响应式的数据应该怎么处理?
在我们的Vue开发中,会有一些数据,从始至终都未曾改变过,这种死数据,既然不改变,那也就不需要对他做响应式处理了,不然只会做一些无用功消耗性能,比如一些写死的下拉框,写死的表格数据,这些数据量大的死数据,如果都进行响应式处理,那会消耗大量性能。
方法一:将数据定义在data的return之外
方法二:Object.freeze()冻结对象
注意点:Object.freeze()冻结的是值,你仍然可以将变量的引用替换掉
// 方法一:将数据定义在data之外
data () {
this.list1 = { xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx }
this.list2 = { xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx }
this.list3 = { xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx }
this.list4 = { xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx }
this.list5 = { xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx }
return {}
}
// 方法二:Object.freeze(), 但是如果在后续方法中直接替换引用
data () {
return {
list1: Object.freeze({xxxxxxxxxxxxxxxxxxxxxxxx}),
list2: Object.freeze({xxxxxxxxxxxxxxxxxxxxxxxx}),
list3: Object.freeze({xxxxxxxxxxxxxxxxxxxxxxxx}),
list4: Object.freeze({xxxxxxxxxxxxxxxxxxxxxxxx}),
list5: Object.freeze({xxxxxxxxxxxxxxxxxxxxxxxx}),
}
}
created () {
// 界面不会有响应
this.list[0].value = 100;
// 下面两种做法,界面都会响应
this.list = [
{ value: 100 },
{ value: 200 }
];
this.list = Object.freeze([
{ value: 100 },
{ value: 200 }
]);
}
watch有哪些属性,分别有什么用?
当监听一个基本数据类型时:
watch: {
value () {
// do something
}
}
当监听一个引用数据类型时:
watch: {
obj: {
handler () { // 执行回调
// do something
},
deep: true, // 是否进行深度监听
immediate: true // 是否初始执行handler函数
}
}
对象新属性无法更新视图,删除属性无法更新视图,为什么?怎么办?
- 原因:Object.defineProperty没有对对象的新属性进行属性劫持
- 对象新属性无法更新视图:使用Vue.$set(obj, key, value),组件中this.$set(obj, key, value)
- 删除属性无法更新视图:使用Vue.$delete(obj, key),组件中this.$delete(obj, key)
直接arr[index] = xxx无法更新视图怎么办?为什么?怎么办?
- 原因:Vue没有对数组进行Object.defineProperty的属性劫持,所以直接arr[index] = xxx是无法更新视图的
- 使用数组的splice方法,arr.splice(index, 1, item)
- 使用Vue.$set(arr, index, value)