6 道面试题
- v-show & v-if 的区别
v-show 元素在树上 只是display去控制是否显示
v-if 元素不上树
使用哪个 具体看该宿主元素是否频繁切换显示
频繁切换:如选项卡等,使用v-show
只显示一遍:使用v-if
- 为啥 v-for 中要用key
因为vue使用的是虚拟dom,即vdom,且在做视图更新时是异步的,会将多次更新融合在一起,
并比较新的vdom和已存在的vdom之间的差别,即diff算法。
在diff算法中,key作为判断两个vnode是否一致的两个条件之一(sel&&key),
当sel&key有一个不一样,diff算法中就认为两个节点不是一样的,就会增加新的或更新掉旧的。
在v-for生成的元素集合中,sel肯定是一样的,所以key就作为了唯一关键判断各自的方式。
- 如果不加key,(或者使用index - unshift()),在diff算法中的updateChildren中的sameChild方法就会认为两个
节点是相同的,就会深度比较,深度比较的时候就会出现一些值的错误(污染),比如checkbox的选中,在
var elm = vnode.elm = oldVnode.elm;这一步,新的vnode就会被重新赋值,产生不必要的错误。
- 如果使用key,当unshift一个data时,每个元素都是独立的,因为key不一致,所以在updateChildren中的sameChild方法
中就会认为是false,此时,新添加的元素对应的vnode就在oldVnodes中找不到,就会preInsert,而不会和
原有的vnode在深度比较中污染。
v-for中不适用key或者key使用不当 造成的问题:
1. checkbox列表,选中第一项,再unshift()一个元素,此时选中的还是第一项。有问题。
- 描述Vue组件生命周期(有无父子组件的情况)
Vue生命周期详解
当有父子组件同时存在的时候:
打开页面
父:beforeCreate created beforeMount
子:beforeCreate created beforeMount mounted
父:mounted
销毁组件
父:beforeDestroy
子:beforeDestroy destroyed
父:destroyed
image.png
- Vue组件如何通讯
值得学习:eventBus进行mixin模式的封装 https://juejin.im/post/5bea35acf265da614e2b9f4b
1. 父子组件
父组件:声明xxx函数到子组件上 子组件通过$emit('xxx')进行触发
2. 平行组件(层级较深的组件)
使用自定义事件,通过公用一个new Vue() 进行使用$emit('xxx') $on('xxx') $('xxx')【一定记得用完销毁】
3. 毫不相干的组件
可以使用vuex进行状态管理
4. 当前组件传值给后面的子孙组件
可以使用provide/reject
在父组件
provide:{
xxx:111
}
在子组件就可以愉快的使用了
reject : [ 'xxx' ]
- 描述组件渲染和更新的过程
- “组件渲染和更新的过程” 简单描述一下
初次渲染
1. initState -> 双向数据绑定,监听getter,setter
2. $mount阶段,将template编译成render函数
3. 执行render函数,读取到data中的数据,读到了,就触发了getter,读不到,就不触发(watcher)。然后生成vnode
4. 进行patch(elem,vnode)
更新
1. 修改数据,触发属性setter
2. 拿到收集到dep中的watcher,派发更新
3. 触发render watcher的render回调(重新执行render函数)
4. 生成新的vnode
5. patch(vnode,newVNode)
- 双向数据绑定v-model的实现原理
1. input元素的value = this.name
2. 绑定oninput事件 this.name = $event.target.value
3. data更新就会触发re-render
一些概念
0. 怎么理解的组件化
- 很久之前(前后端不分离,asp、jsp、php等模板时期)就有了组件化<%@includefile="xxx" %>
- node.js中也有组件化<%- include('xxx',{data:{}}); %>
目前vue、react与传统组件化最大的区别:数据驱动视图
解放对dom的操作,更关注数据、业务。
---- vue 的十分总要的三个重点:
~ 响应式 ~ 模板编译 ~ 虚拟dom
1.如何理解mvvm模型
会画出来mvvm的模型图
image.png
3. vue的响应式
实现原理?
Object.defineProperty(data,()=>{})
实现代码
<script>
// 更新视图
function updateView() {
console.log('视图更新了');
}
// 处理数组
let newArray = Object.create(Array.prototype)
let willMethods = ['push']
willMethods.forEach(m=>{
newArray[m] = function () {
updateView()
return Array.prototype[m].apply(this,arguments)
}
})
// 真正监听
function observerReal(target, key, value) {
observer(value)
Object.defineProperty(target, key, {
get() {
return value
},
set(newVal) {
value = newVal
updateView()
}
})
}
// 判断数据类型以及是否需要监听
function observer(obj) {
if (obj == null || typeof obj !== 'object') {
return
}
if(obj instanceof Array){
obj.__proto__ = newArray
}
for (let key in obj) {
observerReal(obj, key, obj[key])
}
}
// 待监听的数据
let data = {
name: 'whh',
age: 18,
friends: [1, 2, 3],
info: {
city: 'bj'
}
}
// 进行监听
observer(data)
</script>
Object.defineProperty()的一些缺点?
1. 在监听数据的时候,是一次性递归深度监听全部的,计算量大
2. 无法监听‘新增属性’&‘删除属性’
4. 虚拟DOM && diff算法
- vdom是实现vue和React的重要基石
- diff算法是vdom中最最核心、最关键的部分
- render函数,类似vdom,便于理解
为什么出现虚拟dom?有什么历史原因?
- dom操作十分耗时、耗费性能
- 将dom的一些计算,交给js去做,将最小的改动交给dom去更新
翻译下面的html标签到vdom
<div id="div1" class="container">
<p>vdom</p>
<ul style="font-size: 20px">
<li>a</li>
</ul>
</div>
<script>
// 将上述的html标签转义为vdom
let vdom = {
tag:'div',
props:{
id:'div1',
className:'container'
},
children:[
{
tag:'p',
textNode:'vdom',
},
{
tag: 'ul',
props: {
style:'font-size:20px'
},
children:[
{
tag:'li',
textNode: 'a'
}
]
}
]
}
</script>
5. 学习vdom的库是什么?
snabbdom 库 (vue是参考的该库实现的vdom和diff)
https://github.com/snabbdom/snabbdom
简单实践snabbdom
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>snabbdom</title>
</head>
<body>
<div id="container">
</div>
<button id="changeBtn">change button</button>
<script src="https://cdn.bootcss.com/snabbdom/0.7.3/snabbdom.js"></script>
<script src="https://cdn.bootcss.com/snabbdom/0.7.3/snabbdom-class.js"></script>
<script src="https://cdn.bootcss.com/snabbdom/0.7.3/snabbdom-props.js"></script>
<script src="https://cdn.bootcss.com/snabbdom/0.7.3/snabbdom-style.js"></script>
<script src="https://cdn.bootcss.com/snabbdom/0.7.3/snabbdom-eventlisteners.js"></script>
<script src="https://cdn.bootcss.com/snabbdom/0.7.3/h.js"></script>
<script>
const snabbdom = window.snabbdom
// 定义patch
const patch = snabbdom.init({
snabbdom_class,
snabbdom_props,
snabbdom_style,
snabbdom_eventlisteners,
})
// 定义h
const h = snabbdom.h
let container = document.getElementById('container')
// 生成vnode
const vnode = h('ul#list',{},
[
h('li.item',{},'Item 1'),
h('li.item',{},'Item 2')
]
)
patch(container,vnode)
let changeBtn = document.getElementById('changeBtn')
changeBtn.addEventListener('click',function () {
const newVNode = h('ul#list',{},
[
h('li.item',{},'Item 1.1'),
h('li.item',{},'Item 2')
]
)
patch(vnode,newVNode)
})
</script>
</body>
</html>
6. diff算法
找出两个vdom之间的最小区别,进行更新
7. 树diff的时间复杂度
On^3 在左边第一个节点开始,每一个节点,就遍历右边一遍,此时就是On^2,
并且在遍历时找到差异后还要计算最小转换方式,就是On^3。1000个节点,就要遍历1亿次,算法几乎不可用。
Vue和React的解决方案比较粗暴,即遍历左边的时候,同位置去右边拿值,相同就不变,
不同就替换或删掉,一次循环就结束,即时间复杂度为On。
- 只比较同一层级,不跨级比较
- tag不相同,直接删掉重建,不再深度比较
- tag和key,两者都相同,则认为是相同的节点,不再深度比较。
snabbdom的patch函数的实现逻辑(diff算法的主要逻辑)
8. vue的模板
- 模板是什么 简单描述一下
1. 模板不是html,其有指令、插值、JS表达式、能实现判断、循环
2. html是标签语言,图灵不完备;js可以实现循环、判断,是图灵完备的语言。
3. 因此,模板肯定是转换成js代码,才可以实现各种功能
- “组件渲染和更新的过程” 简单描述一下
初次渲染
1. initState -> 双向数据绑定,监听getter,setter
2. $mount阶段,将template编译成render函数
3. 执行render函数,读取到data中的数据,读到了,就触发了getter,读不到,就不触发(watcher)。然后生成vnode
4. 进行patch(elem,vnode)
更新
1. 修改数据,触发属性setter
2. 拿到收集到dep中的watcher,派发更新
3. 触发render watcher的render回调(重新执行render函数)
4. 生成新的vnode
5. patch(vnode,newVNode)
vue组件渲染和更新的过程
- vue是使用什么工具将模板编译到render函数的?
vue-template-complier 工具
- with语法是什么意思
// 使用with,能改变{}内自由变量的查找方式
// 将{}内自由变量,当做obj的属性去查找,找不到就报错
let obj = {a:100,b:200}
with (obj) {
console.log(a); // 100
console.log(b); // 200
console.log(c); // 报引用错误 ReferenceError: c is not defined
}
- vue 使用vue-template-compiler 将template编译成render函数
let template = `<div>{{name}}</div>`
compile.compile(template).render
- vue源码中的一些简写 -c -s -v
-c createElement,类似于snabbdom中的h函数,返回的也是vnode
-s toString() 转换成字符串
-v createTextVNode 创建一个文本节点
- vue渲染的时候是同步渲染还是异步渲染呢?
是异步渲染。就比如:$nextTick();
目的:汇总data的修改,一次性更新视图;减少DOM的操作次数,提升性能。
- 前端路由的原理(通用)
路由模式: hash history
- hash的特点,即为什么hash可以作为前端路由的实现模式?
1. hash的变化会触发网页的跳转,即浏览器的前进与后退(最根本)
2. hash的变化不会刷新页面,这也是SPA项目必备的特点
3. hash是不会提交到server端,即使强制刷新
- hash路由是基于哪个事件监听实现的?
window.onhashchange = function(event){}
- history 简介
1. history是一个规范的路由,跳转的时候也不刷新页面
- history 的实现原理
history.pushState && history.replaceState
- history模式是基于哪个事件监听实现的?
window.onpopstate
- history为什么需要后端支持?
在某个路由页面比如:www.xxx.com/user/user-detail 页面的时候,
此时强制刷新,浏览器会向后端请求www.xxx.com/user/user-detail对应的资源,肯定没有,所以会出404错误。
解决方案: 在后端判断一下,当前项目,无论请求什么页面,都返回index.html(原始页)页面即可
Vue的面试真题演练
1. 对mvvm的理解
看图
2. computed的特点
- 缓存,data不变就不会重新计算
3. 为何组件的data必须是一个函数
- vue文件编译后 是生成一个class,在哪里用到,就new一个出来,如果data不是函数的话,那么new出来的各个对象就
会公用一个data(在堆里找的是同一个data对象),造成污染。
当data是个函数的时候,在new出新组件后,由于function的缘故,会不同组件之间data的作用域,就不会形成污染。
4. ajax请求应该放在哪个生命周期?
mounted
因为js是单线程的,ajax异步获取数据。
就算放到created中,created后边要执行mount,边要发起请求,其实没必要,会让流程分支,所以建议放在mounted中。
5. 怎样将组件所有的props传递给子组件?
<User v-bind="$props"/>
6. 如何自己实现v-model?
// 子组件
<template>
<div>
<input type="text" :value="name" @input="$emit('change',$event.target.value)">
</div>
</template>
<script>
export default {
name: "index",
model:{
prop:'name',
event:'change'
},
props:{
name:{
type:String,
default:()=>''
}
}
}
</script>
// 父组件
<VModel v-model="name"></VModel>
7. 多个组件有相同的逻辑,如何抽离?
1.mixin
8. 何时使用异步组件?
1. 加载大组件,比如编辑器..
2. 路由异步加载
9. 何时使用keep-alive
1. 缓存组件,不需要重复渲染
2. 如多个静态的tab页的切换
3. 优化性能
10. 何时使用beforeDestory
1. 解绑自定义事件 event.$off
2. 清除定时器
2. 解绑自己定义的dom事件,比如window.scroll等,vue自带的会自己解除
11. 什么是作用域插槽
1. 在组件内,将一些不确定的东西交给父节点去补充,并且把渲染时的作用域内的值交出去
// 父组件 <template v-slot='scope'>scope.xxx</template>
// 子组件 <slot :scope='scope'> </slot>
12. vuex中的action和mutation有什么区别
1. action中处理异步,mutation中不可以
2. mutation中做原子操作
3. action可以整合多个mutation
13. 请描述响应式的原理
1. 监听data变化
2. 组件渲染和更新的流程
14. vue常见性能优化
1. 合理使用v-show v-if
2. 合理使用computed
3. v-for中加key,以及避免和v-if一起使用(v-for优先级高,先把所有代码块都for出来,再看v-if,把不要的代码块去掉,很不优雅),
解决方案:v-for的对象可以是computed计算后的列表,这样最好;如果实在不行,就用v-show替代v-if,可以共存。
4. 自定义事件,dom事件及时销毁。
5. 合理使用异步组件
6. 合理使用keep-alive
7. data的层级不要太深,响应式监听的时候递归时间就会长。
8. webpack层级的优化 ... 待补充
9. 前端通用性能优化,图片懒加载等等...
# 前端通用性能优化
原则: 空间换时间
1. 多使用内存,缓存或者其他方法
2. 减少CPU的计算量,减少网络加载耗时
从何入手:
让加载更快
减少资源体积 - 压缩代码
减少访问次数
合并代码(合并js等资源)
SSR服务端渲染(把资源在后端一次组装完成,减少客户端的请求次数)
缓存(静态资源加hash后缀,根据文件计算hash。文件不变,则会自动触发http缓存机制,返回304)
使用更快的网络 - CDN
让渲染更快
CSS放在head,JS放在body最下面
尽早开始执行JS,在DOMContentLoaded触发
懒加载(图片懒加载,上滑加载更多...)
对DOM查询进行缓存
避免频繁的DOM操作,使用DocumentFragement片段
节流、防抖
网友评论