1、你了解diff算法吗?
简单总结:
patchVnode函数:
-
如果
oldVnode
有子节点而VNode
没有,则删除el
子节点 -
如果
oldVnode
没有子节点而VNode
有,则将VNode
的子节点真实化后添加到el
-
如果两者都有子节点,则执行
updateChildren
函数比较子节点 (核心diff算法)
updateChildren
函数具体逻辑执行顺序:
updateChildren
温馨提示:算法顺序:
- 头头比较
- 尾尾比较
- old头new尾
- old尾new头
- 如果上述都不匹配:尝试在oldChildren中寻找和newStartVnode的具有相同key的Vnode
详见:https://liulibin.blog.youkuaiyun.com/article/details/114916799
2、vue常用的修饰符有哪些?应用场景
表单修饰符:
- lazy:在我们填完信息,光标离开标签的时候,才会将值赋予给
value
,也就是在change
事件之后再进行信息同步 - trim:自动过滤用户输入的首空格字符,而中间的空格不会过滤
- number:自动将用户的输入值转为数值类型,但如果这个值无法被
parseFloat
解析,则会返回原来的值
<input type="text" v-model.lazy="value">
<input type="text" v-model.trim="value">
<input v-model.number="age" type="number">
事件修饰符:
-
stop 阻止了事件冒泡,相当于调用了
event.stopPropagation
方法 -
prevent 阻止了事件的默认行为,相当于调用了
event.preventDefault
方法 -
self 只当在
event.target
是当前元素自身时触发处理函数 -
once 绑定了事件以后只能触发一次,第二次就不会触发
-
capture 使事件触发从包含这个元素的顶层开始往下触发
-
passive 在移动端,当我们在监听元素滚动事件的时候,会一直触发
onscroll
事件会让我们的网页变卡,因此我们使用这个修饰符的时候,相当于给onscroll
事件整了一个.lazy
修饰符 -
native 让组件变成像
html
内置标签那样监听根元素的原生事件,否则组件上使用v-on
只会监听自定义事件
鼠标按钮修饰符:
-
left 左键点击
-
right 右键点击
-
middle 中键点击
键盘修饰符:
-
普通键(enter、tab、delete、space、esc、up...)
-
系统修饰键(ctrl、alt、meta、shift...)
详见:https://liulibin.blog.youkuaiyun.com/article/details/114964657
3、循环绑定key值有必要写吗?为什么
当 Vue.js 用 v-for 正在更新已渲染过的元素列表时,它默认用“就地复用”策略。如果数据项的顺序被改变,Vue 将不会移动 DOM 元素来匹配数据项的顺序, 而是简单复用此处每个元素,并且确保它在特定索引下显示已被渲染过的每个元素。key的作用主要是为了高效的更新虚拟DOM
详见:https://liulibin.blog.youkuaiyun.com/article/details/114966729
4、说说你对keep-alive的理解是什么?怎么缓存当前的组件?缓存后怎么更新?
keep-alive
可以设置以下props
属性:
-
include
- 字符串或正则表达式。只有名称匹配的组件会被缓存 -
exclude
- 字符串或正则表达式。任何名称匹配的组件都不会被缓存 -
max
- 数字。最多可以缓存多少组件实例
设置了 keep-alive 缓存的组件,会多出两个生命周期钩子(activated
与deactivated
):
-
首次进入组件时:
beforeRouteEnter
>beforeCreate
>created
>mounted
>activated
> ... ... >beforeRouteLeave
>deactivated
-
再次进入组件时:
beforeRouteEnter
>activated
> ... ... >beforeRouteLeave
>deactivated
举个栗子:
当我们从首页
–>列表页
–>商详页
–>再返回
,这时候列表页应该是需要keep-alive
从首页
–>列表页
–>商详页
–>返回到列表页(需要缓存)
–>返回到首页(需要缓存)
–>再次进入列表页(不需要缓存)
,这时候可以按需来控制页面的keep-alive
注意:服务器端渲染期间avtived
不被调用
详见:https://liulibin.blog.youkuaiyun.com/article/details/105050112
5、你有写过自定义指令吗?自定义指令的应用场景有哪些?
详见:https://liulibin.blog.youkuaiyun.com/article/details/114966397
6、什么是虚拟DOM?如何实现一个虚拟DOM?
7、vue组件中data为什么必须是一个函数?
因为JavaScript的特性所导致,在component中,data必须以函数的形式存在,不可以是对象。
组建中的data写成一个函数,数据以函数返回值的形式定义,这样每次复用组件的时候,都会返回一份新的data,相当于每个组件实例都有自己私有的数据空间,它们只负责各自维护的数据,不会造成混乱。而单纯的写成对象形式,就是所有的组件实例共用了一个data,这样改一个全都改了。
8、vue生命周期的理解、执行顺序
vue3生命周期
beforecate
和created
(它们被setup
方法本身所取代)- onBeforeMount – 在挂载开始之前被调用:相关的 render 函数首次被调用。
- onMounted – 组件挂载时调用
- onBeforeUpdate – 数据更新时调用,发生在虚拟 DOM 打补丁之前。这里适合在更新之前访问现有的 DOM,比如手动移除已添加的事件监听器。
- onUpdated – 由于数据更改导致的虚拟 DOM 重新渲染和打补丁,在这之后会调用该钩子。
- onBeforeUnmount – 在卸载组件实例之前调用。在这个阶段,实例仍然是完全正常的。
- onUnmounted – 卸载组件实例后调用。调用此钩子时,组件实例的所有指令都被解除绑定,所有事件侦听器都被移除,所有子组件实例被卸载。
- onActivated – 被 keep-alive 缓存的组件激活时调用。
- onDeactivated – 被 keep-alive 缓存的组件停用时调用。
- onErrorCaptured – 当捕获一个来自子孙组件的错误时被调用。此钩子会收到三个参数:错误对象、发生错误的组件实例以及一个包含错误来源信息的字符串。此钩子可以返回 false 以阻止该错误继续向上传播
对比:
- beforeCreate -> 使用 setup()
- created -> 使用 setup()
- beforeMount -> onBeforeMount
- mounted -> onMounted
- beforeUpdate -> onBeforeUpdate
- updated -> onUpdated
- beforeDestroy -> onBeforeUnmount
- destroyed -> onUnmounted
- errorCaptured -> onErrorCaptured
9、父子组件传值?你知道的传值有哪些
-
父子关系的组件数据传递选择
props
与$emit
进行传递,也可选择ref
-
兄弟关系的组件数据传递可选择
$bus
,其次可以选择$parent
进行传递 -
祖先与后代组件数据传递可选择
attrs
与listeners
或者Provide
与Inject
-
复杂关系的组件数据传递可以通过
vuex
存放共享的变量
详见:https://liulibin.blog.youkuaiyun.com/article/details/115064992
10、router和route的区别
$route是“路由信息对象”,包括path,params,hash,query,fullPath,matched,name等路由信息参数。而$router是“路由实例”对象包括了路由的跳转方法,钩子函数等
11、vue-router导航钩子
全局导航钩子:beforeEach(to, from, next)、beforeResolve(to, from, next)、afterEach(to, from ,next)
组件内钩子:beforeRouteEnter、beforeRouteUpdate、beforeRouteLeave
单独路由独享组件:beforeEnter
12、vue2的双向数据绑定原理
13、vue3的双向数据绑定原理
14、你都做过哪些Vue的性能优化?
编码阶段
- 尽量减少data中的数据,data中的数据都会增加getter和setter,会收集对应的watcher
- v-if和v-for不能连用
- 如果需要使用v-for给每项元素绑定事件时使用事件代理
- SPA 页面采用keep-alive缓存组件
- 在更多的情况下,使用v-if替代v-show
- key保证唯一
- 使用路由懒加载、异步组件
- 防抖、节流
- 第三方模块按需导入
- 长列表滚动到可视区域动态加载
- 图片懒加载
SEO优化
- 预渲染
- 服务端渲染SSR
打包优化
- 压缩代码
- Tree Shaking/Scope Hoisting
- 使用cdn加载第三方模块
- 多线程打包happypack
- splitChunks抽离公共文件
- sourceMap优化
用户体验
- 骨架屏
- PWA
还可以使用缓存(客户端缓存、服务端缓存)优化、服务端开启gzip压缩等。
15、hash路由和history路由实现原理说一下
hash : window.onhashchange
history : history.pushState 和 window.onpopstate
16、你的接口请求一般放在哪个生命周期中?
接口请求一般放在mounted中,但需要注意的是服务端渲染时不支持mounted,需要放到created中。
17、虚拟Dom以及key属性的作用
由于在浏览器中操作DOM是很昂贵的。频繁的操作DOM,会产生一定的性能问题。这就是虚拟Dom的产生原因。
Vue2的Virtual DOM借鉴了开源库snabbdom的实现。
Virtual DOM本质就是用一个原生的JS对象去描述一个DOM节点。是对真实DOM的一层抽象。(也就是源码中的VNode类,它定义在src/core/vdom/vnode.js中。)
VirtualDOM映射到真实DOM要经历VNode的create、diff、patch等阶段。
「key的作用是尽可能的复用 DOM 元素。」
新旧 children 中的节点只有顺序是不同的时候,最佳的操作应该是通过移动元素的位置来达到更新的目的。
需要在新旧 children 的节点中保存映射关系,以便能够在旧 children 的节点中找到可复用的节点。key也就是children中节点的唯一标识。
16、Computed/Watch/methods
computed:
- 如果绑定的是v-model,则set/get方法都要有,默认是get
- 计算属性是基于它们的依赖进行缓存的,只在相关依赖发生改变时它们才会重新求值
watch:主要用于监控vue实例的变化,它监控的变量当然必须在data里面声明才可以,它可以监控一个变量,也可以是一个对象
- deep:true 对应的是引用类型
- 默认是false , 基本类型
methods:则必须要有一定的触发条件才能执行,如点击事件;
18、vue.nextTick()的理解及使用
在下次 DOM 更新循环结束之后执行延迟回调。在修改数据之后立即使用这个方法,获取更新后的 DOM
详见:https://liulibin.blog.youkuaiyun.com/article/details/115000337
19、Vue.js中ajax请求代码应该写在组件的methods中还是vuex的actions中?
20、vuex有哪几种属性?
21、说说你对vue的mixin的理解,有哪些应用场景?
在日常的开发中,我们经常会遇到在不同的组件中经常会需要用到一些相同或者相似的代码,这些代码的功能相对独立
这时,可以通过Vue
的mixin
功能将相同或者相似的代码提出来
详见:https://liulibin.blog.youkuaiyun.com/article/details/114999149
22、SPA(单页应用)首屏加载速度慢怎么解决?
加载慢的原因:
在页面渲染的过程,导致加载速度慢的因素可能如下:
-
网络延时问题
-
资源文件体积是否过大
-
资源是否重复发送请求去加载了
-
加载脚本的时候,渲染内容堵塞了
解决方案:
常见的几种SPA首屏优化方式
-
减小入口文件积
-
静态资源本地缓存
-
UI框架按需加载
-
图片资源的压缩
-
组件重复打包
-
开启GZip压缩
-
使用SSR
详见:https://liulibin.blog.youkuaiyun.com/article/details/115064769
23、如何给SPA做SEO
下面给出基于Vue
的SPA
如何实现SEO
的三种方式
-
SSR服务端渲染
将组件或页面通过服务器生成html,再返回给浏览器,如nuxt.js
-
静态化
目前主流的静态化主要有两种:
(1)一种是通过程序将动态页面抓取并保存为静态页面,这样的页面的实际存在于服务器的硬盘中
(2)另外一种是通过WEB服务器的 URL Rewrite
的方式,它的原理是通过web服务器内部模块按一定规则将外部的URL请求转化为内部的文件地址,一句话来说就是把外部请求的静态地址转化为实际的动态页面地址,而静态页面实际是不存在的。这两种方法都达到了实现URL静态化的效果
-
使用
Phantomjs
针对爬虫处理
原理是通过Nginx
配置,判断访问来源是否为爬虫,如果是则搜索引擎的爬虫请求会转发到一个node server
,再通过PhantomJS
来解析完整的HTML
,返回给爬虫。
24、Vue3.0里为什么要用 Proxy API 替代 defineProperty API ?
Object.defineProperty
只能遍历对象属性进行劫持
Proxy
直接可以劫持整个对象,并返回一个新对象,我们可以只操作新的对象达到响应式目的
Proxy
有多达13种拦截方法,不限于apply
、ownKeys
、deleteProperty
、has
等等,这是Object.defineProperty
不具备的
正因为defineProperty
自身的缺陷,导致Vue2
在实现响应式过程需要实现其他的方法辅助(如重写数组方法、增加额外set
、delete
方法)
Proxy不兼容IE,也没有 polyfill, defineProperty能支持到IE9
详见:https://liulibin.blog.youkuaiyun.com/article/details/115078363
25、Vue3.0的设计目标是什么?做了哪些优化?
详见:https://liulibin.blog.youkuaiyun.com/article/details/115078834
26、Vue3.0 性能提升主要是通过哪几方面体现的?
详见:https://liulibin.blog.youkuaiyun.com/article/details/115079218
27、Vue3.0 所采用的 Composition Api 与 Vue2.x 使用的 Options Api 有什么不同?
-
在逻辑组织和逻辑复用方面,
Composition API
是优于Options API
-
因为
Composition API
几乎是函数,会有更好的类型推断。 -
Composition API
对tree-shaking
友好,代码也更容易压缩 -
Composition API
中见不到this
的使用,减少了this
指向不明的情况 -
如果是小型组件,可以继续使用
Options API
,也是十分友好的
详见:https://liulibin.blog.youkuaiyun.com/article/details/115079453
28、说说Vue 3.0中Treeshaking特性?举例说明一下?
通过Tree shaking
,Vue3
给我们带来的好处是:
-
减少程序体积(更小)
-
减少程序执行时间(更快)
-
便于将来对程序架构进行优化(更友好)
详见:https://liulibin.blog.youkuaiyun.com/article/details/115080332
29、兄弟组件之间传递 或者 层级比较深 自定义事件传递
$on $emit $off(关闭 beforeDestroy)
event.js
```
import vue from "vue"
export default new vue()
```
30、slot插槽
具名插槽、作用域插槽
31、动态组件
场景:例如:详情页里面的模块顺序不唯一,动态组件循环去渲染对应顺序组件
```
<component :is="comName"/>
```
32、异步组件
场景:页面加载性能优化,初始化时不加载,配合v-if
<script>
import name from "../ddd/name.vue" //同步加载
export default {
components:{
name,
name:()=>import("../ddd/name.vue) //异步加载
}
}
</script>
33、组件抽离公共逻辑 mixin
多个组件有相同的逻辑,抽离出来(例如,自己封装了一个组件,有多个子传父的方法,可以使用,或者多个组件相同业务逻辑也可使用)
```
<script>
import mymixin from "mymixin.js"
export default {
mixins:[mymixin],
mounted(){
this.showName()// 获取 mymixin 方法等
}
}
</script>
```
mixin.js
```
export default {
data(){
return {
}
},
methods:{
showName(){
console.log()
}
},
mounted(){
}
}
```
34、何时需要使用beforeDestroy
1、解除自定义事件event.$off
2、清楚计时器
3、解绑自定义的DOM事件,如window scroll等
35、监听data变化的核心api是什么
object.defineProperty()
36、vue如何监听数组变化
object.defineProperty不能监听数组变化
重新定义原型,重写push/pop等方法,实现监听
见 core/observer/array.js
const arrayProto = Array.prototype
const arrayMethods = Object.create(arrayProto)
37、类似于 window.alert() 提示组件,怎么实现
Vue.extend + vm.$mount 组合
38、vue初始化页面闪动问题
34、$set
// 使用
Vue $sett(object, key, value)
function set (target: Array<any> | Object, key: any, val: any): any {
if (process.env.NODE_ENV !== 'production' &&
(isUndef(target) || isPrimitive(target))
) {
warn(`Cannot set reactive property on undefined, null, or primitive value: ${(target: any)}`)
}
// 数组
if (Array.isArray(target) && isValidArrayIndex(key)) {
target.length = Math.max(target.length, key)
target.splice(key, 1, val)
return val
}
// 对象
if (key in target && !(key in Object.prototype)) {
target[key] = val
return val
}
const ob = (target: any).__ob__
if (target._isVue || (ob && ob.vmCount)) {
process.env.NODE_ENV !== 'production' && warn(
'Avoid adding reactive properties to a Vue instance or its root $data ' +
'at runtime - declare it upfront in the data option.'
)
return val
}
if (!ob) {
target[key] = val
return val
}
defineReactive(ob.value, key, val)
ob.dep.notify()
return val
}
操作数组的更新方法:使用splice
替换将数组的旧值替换成了新值
操作对象的方法,比数组稍微麻烦一些:
- 首先
if (key in target && !(key in Object.prototype))
判断这个key是不是当前对象的key 和 这个key 不是object原型的key。说明这个key本来就在对象上面已经定义过了的,直接修改值就可以了,可以自动触发响应。 - 这时候给对象添加了
__obj__
属性,然后又做了判断if (target._isVue || (ob && ob.vmCount))
的意思是:当前的target对象是vue实例对象或者是根数据对象,那么就会抛出错误警告 - 这一步才是最核心的地方
defineReactive(ob.value, key, val)
这个方法的意思是给新加的属性添加依赖,以后再直接修改这个新的属性的时候就会触发页面渲染。ob.dep.notify()
这个方法可以理解成渲染函数,所以页面就会进行重新渲染