介绍目前常见前端面试题,Vue 基础原理题
参考慕课网实战课 前端框架及项目面试-聚焦 Vue3 React Webpack 总结的知识点
Vue 基本使用
computed 和watch
- computed 有缓存,data不变则不会重新计算
- watch如何深度监听?
- watch 监听引用类型,拿不到oldVal
class 和 style
- 使用动态属性
- 使用驼峰式写法
条件渲染
- V-if v-else的用法,可使用变量,也可以使用===表达式
- v-if 和 V-show的区别?
- v-if 和 V-show的使用场景?
循环(列表)渲染
- 如何遍历对象?——也可以用V-for
- key的重要性。key 不能乱写(如random或者index)
- V-for和v-if不能一起使用!
事件
event参数,自定义参数
- event 是原生的
- 事件被挂载到当前元素
- 和 DOM 事件一样
事件修饰符,按键修饰符
事件修饰符
1 | <!--阻止单击事件继续传播--> |
按键修饰符
1 | <!--即使ALt或Shift被一同按下时也会触发--> |
- 【观察】事件被绑定到哪里?
表单
- v-model
- 常见表单项textarea checkbox radio select
- 修饰符lazy number trim
Vue 组件使用
- props和$emit
- 组件间通讯-自定义事件
- 组件生命周期
生命周期(单个组件)
- 挂载阶段 created mounted
- 更新阶段 updated
- 销毁阶段 destroyed
created mounted的区别
el temp之间,create Vue实例初始化了,但还没渲染完成,mount已经渲染完成
Vue 高级特性
- 不是每个都很常用,但用到的时候必须要知道
- 考察候选人对Vue的掌握是否全面,且有深度
- 考察做过的项目是否有深度和复杂度(至少能用到高级特性)
Vue 高级特性
- 自定义v-model
- $nextTick
- slot
- 动态、异步组件
- keep-alive
- mixin
$nextTick
- Vue 是异步渲染(原理部分会详细讲解)
- data改变之后,DOM不会立刻渲染
- $nextTick会在DOM 渲染之后被触发,以获取最新DOM节点
slot
- 基本使用
- 作用域插槽
- 具名插槽
动态组件
- :is=”component-name”用法
- 需要根据数据,动态渲染的场景。即组件类型不确定。
异步组件
- import()函数
- 按需加载,异步加载大组件
keep-alive
- 缓存组件
- 频繁切换,不需要重复渲染
- Vue常见性能优化
应用场景:tab切换
在Vue层级里面缓存切换,而v-show是在css层级display:none里
mixin
- 多个组件有相同的逻辑,抽离出来
- mixin 并不是完美的解决方案,会有一些问题
- Vue3提出的Composition API 旨在解决这些问题
mixin的问题
- 变量来源不明确,不利于阅读
- 多mixin 可能会造成命名冲突
- mixin 和组件可能出现多对多的关系,复杂度较高
相关的面试技巧
- 可以不太深入,但必须知道
- 熟悉基本用法,了解使用场景
- 最好能和自己的项目经验结合起来
Vuex 使用
- 面试考点并不多(因为熟悉Vue之后,vuex 没有难度)
- 但基本概念、基本使用和API必须要掌握
- 可能会考察 state的数据结构设计(后面会讲)
Vuex 基本概念
- state
- mutation
- getters
- action
用于Vue组件
- dispatch
- commit
- mapState
- mapGetters
- mapActions
- mapMutations
Actions才能做异步操作
Vue-router 使用
- 面试考点并不多(前提是熟悉Vue)
- 路由模式(hash、H5 history)
- 路由配置(动态路由、懒加载)
Vue-router 路由模式
- hash模式(默认),如http://abc.com/#/user/10
- H5 history 模式,如http://abc.com/user/20
- 后者需要server 端支持,因此无特殊需求可选择前者
1 | const router = new VueRouter({ |
动态路由
1 | const User={ |
懒加载
1 | export default new VueRouter({ |
Vuex Vue-router总结
- 面试考点并不多(前提是熟悉Vue)
- 掌握基本概念,基本使用
- 面试官时间有限,需考察最核心、最常用的问题,而非边角问题
Vue 原理(大厂必考)
- 面试为何会考察原理?
- 之前然知其所以然——各行业通用的道理
- 了解原理,才能应用的更好(竞争激烈,择优录取)
- 大厂造轮子(有钱有资源,业务定制,技术KPI)
- 面试中如何考察?以何种方式?
- 考察重点,而不是考察细节。掌握好2/8原则
- 和使用相关联的原理,例如vdom、模板渲染
- 整体流程是否全面?热门技术是否有深度?
- Vue 原理包括哪些?
- 组件化
- 响应式
- vdom和diff
- 模板编译
- 渲染过程
- 前端路由
组件化基础
- “很久以前“就有组件化
- asp jsp php 已经有组件化了
- nodejs 中也有类似的组件化
- 数据驱动视图(MVVM,setState)
- 传统组件,只是静态渲染,更新还要依赖于操作DOM
- 数据驱动视图-Vue MVVM
- 数据驱动视图-React setState(暂时按下不表)
Vue 响应式
- 组件data的数据一旦变化,立刻触发视图的更新
- 实现数据驱动视图的第一步
- 考察Vue原理的第一题
- 核心API-Object.defineProperty
- 如何实现响应式,代码演示
- Object.defineProperty的一些缺点(Vue3.0启用Proxy)
- Proxy 兼容性不好,且无法polyfill
- Vue2.x还会存在一段时间,所以都得学
- Vue3.0相关知识,下一章讲,这里只是先提一下
Object.defineProperty 基本用法
1 | const data = {} |
Object.defineProperty 实现响应式
- 如何监听对象(深度监听),监听数组(需要特殊处理)
- 复杂对象,深度监听
- Object.defineProperty 缺点:
- 深度监听,需要递归到底,一次性计算量大
- 无法监听新增属性/删除属性(Vue.set Vue.delete)
- 无法原生监听数组,需要特殊处理
虚拟DOM(Virtual DOM)和diff
- vdom 是实现vue和React的重要基石
- dif 算法是vdom中最核心、最关键的部分
- vdom是一个热门话题,也是面试中的热门问题
VDOM
原因:
- DOM操作非常耗费性能
- 以前用jQuery,可以自行控制DOM操作的时机,手动调整
- Vue和React 是数据驱动视图,如何有效控制DOM操作?
解决方案-vdom
- 有了一定复杂度,想减少计算次数比较难
- 能不能把计算,更多的转移为JS计算?因为JS执行速度很快
- vdom-用JS模拟DOM结构,计算出最小的变更,操作DOM
通过snabbdom学习vdom
简洁强大的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操作
diff 算法
- diff 算法是vdom中最核心、最关键的部分
- diff 算法能在日常使用vue React中体现出来(如key)
- dif 算法是前端热门话题,面试“宠儿”
diff 算法概述
- diff即对比,是一个广泛的概念,如linux diff命令、git diff等
- 两个js对象也可以做 diff,如 https://github.com/cujojs/jiff
- 两棵树做 diff,如这里的vdom diff
树 diff 的时间复杂度O(n^3)
- 第一,遍历tree1
- 第二,遍历tree2
- 第三,排序
- 1000个节点,要计算1亿次,算法不可用
优化时间复杂度到 O(n)
- 只比较同一层级,不跨级比较
- tag不相同,则直接删掉重建,不再深度比较
- tag和key,两者都相同,则认为是相同节点,不再深度比较
diff 算法总结
- patchVnode
- addVnodes removeVnodes
- updateChildren(key的重要性)
vdom 和 diff-总结
- 细节不重要,updateChildren的过程也不重要,不要深究
- vdom核心概念很重要:h、vnode、patch、diff、key等
- vdom存在的价值更加重要:数据驱动视图,控制DOM操作
模板编译
- 模板是vue开发中最常用的部分,即与使用相关联的原理
- 它不是html,有指令、插值、JS表达式,到底是什么?
- 面试不会直接问,但会通过“组件渲染和更新过程”考察
原理
- 前置知识:JS 的 with 语法
- 改变 {} 内自由变量的查找规则,当做 obj 属性来查找
- 如果找不到匹配的 obj 属性,就会报错
- with要慎用,它打破了作用域规则,易读性变差
- vue template complier 将模板编译为 render 函数
- 执行 render 函数生成 vnode
编译模板
- 模板不是html,有指令、插值、JS表达式,能实现判断、循环
- html是标签语言,只有JS才能实现判断、循环(图灵完备的)
- 因此,模板一定是转换为某种JS代码,即编译模板
- 原理:
- 模板编译为render函数,执行render 函数返回vnode
- 基于vnode 再执行patch 和diff(后面会讲)
- 使用webpack vue-loader,会在开发环境下编译模板(重要)
- vue 组件中使用render代替 template
- 讲完模板编译,再讲这个render,就比较好理解了
- 在有些复杂情况中,不能用template,可以考虑用 render
- React 一直都用render(没有模板),和这里一样
1 | Vue.component('heading',{ |
总结
- with 语法
- 模板到 render 函数,再到 vnode,再到渲染和更新
- vue 组件可以用 render 代替 template
组件渲染/更新过程
- 一个组件渲染到页面,修改data触发更新(数据驱动视图)
- 其背后原理是什么,需要掌握哪些要点?
- 考察对流程了解的全面程度
回顾学过的知识
- 响应式:监听 data 属性 getter setter(包括数组)
- 模板编译:模板到render函数,再到vnode
- vdom:patch(elem,vnode)和patch(vnode,newVnode)
组件渲染/更新过程
- 初次渲染过程
- 解析模板为render函数(或在开发环境已完成,vue-loader)
- 触发响应式,监听 data 属性 getter setter
- 执行render函数,生成vnode,patch(elem,vnode)
- 注意点 :执行 render 函数会触发 getter
- 更新过程
- 修改data,触发setter(此前在getter中已被监听)
- 重新执行render 函数,生成newVnode
- patch(vnode,newVnode)
- 异步渲染
- 回顾$nextTick
- 汇总data的修改,一次性更新视图
- 减少DOM操作次数,提高性能
总结
- 渲染和响应式的关系
- 渲染和模板编译的关系
- 渲染和vdom的关系
- 初次渲染过程
- 更新过程
- 异步渲染
前端路由原理
- 稍微复杂一点的SPA(单页面应用),都需要路由
- vue-router 也是vue全家桶的标配之一
- 属于“和日常使用相关联的原理”,面试常考
- 回顾 vue-router 的路由模式
- hash(默认)
- hash变化会触发网页跳转,即浏览器的前进、后退
- hash 变化不会刷新页面,SPA必需的特点
- hash 永远不会提交到server端(前端自生自灭)
- H5 history
- 用url规范的路由,但跳转时不刷新页面
- history.pushState
- window.onpopstate
- hash(默认)
总结
- hash - window.onhashchange
- H5 history - history.pushState 和 window.onpopstate
- H5 history 需要后端支持
两者选择
- to B 的系统推荐用 hash,简单易用,对url规范不敏感
- 后台管理页面
- to C 的系统,可以考虑选择 H5 history,但需要服务端支持
- 能选择简单的,就别用复杂的,要考虑成本和收益
面试题
❤ v-if 和 v-show区别
- 得分点 v-show true/false都渲染 、 v-if true渲染 false不渲染
- 标准回答
- 作用: 都是控制元素隐藏和显示的指令
- 区别:
- v-show: 控制的元素无论是true还是false,都被渲染出来了,通过CSS display:none控制元素隐藏
- v-if: 控制的元素是true,进行渲染,如果是false不渲染,根本在dom树结构中不显示
- 加分回答
- 应用:
- v-show: 适合使用在切换频繁显示/隐藏的元素上
- v-if: 适合使用在切换不频繁,且元素内容很多,渲染一次性能消耗很大的元素上
- 应用:
❤ v-for 为什么要用 key
- 得分点 性能优化、diff算法节点比对、key不能是index
- 标准回答
- 为了性能优化 因为vue是虚拟DOM,更新DOM时用diff算法对节点进行一一比对,比如有很多li元素,要在某个位置插入一个li元素,但没有给li上加key,那么在进行运算的时候,就会将所有li元素重新渲染一遍,但是如果有key,那么它就会按照key一一比对li元素,只需要创建新的li元素,插入即可,不需要对其他元素进行修改和重新渲染。
- 必须用key,且不能是index和random
- 原理:diff 算法中通过 tag 和 key 来判断,是否是 sameNode
- 效果:减少渲染次数,提升渲染性能
- 加分回答
- key也不能是li元素的index,因为假设我们给数组前插入一个新元素,它的下标是0,那么和原来的第一个元素重复了,整个数组的key都发生了改变,这样就跟没有key的情况一样了
❤ Vue 组件生命周期
得分点 beforeCreate、created、beforeMounted、mounted beforeUpdate、updated 、 beforeDestroy、destroyed
标准回答
- 钩子函数用来描述一个组件从引入到退出的全过程中的某个过程,整个过程称为生命周期。
- 钩子函数按照组件生命周期的过程分为,挂载阶段=>更新阶段=>销毁阶段。
- 每个阶段对应的钩子函数
- 挂载阶段:beforeCreate、created、beforeMounted、mounted
- 更新阶段:beforeUpdate、updated
- 销毁阶段:beforeDestroy、destroyed
- 每个阶段特点与适合做什么
created
:实例创建完成,可访问data、computed、watch、methods上的方法和数据,未挂载到DOM,不能访问到el属性,el属性,ref属性内容为空数组常用于简单的ajax请求,页面的初始化beforeMount
:在挂载开始之前被调用,beforeMount之前,会找到对应的template,并编译成render函数mounted
:实例挂载到DOM上,此时可以通过DOM API获取到DOM节点,$ref属性可以访问常用于获取VNode信息和操作,ajax请求beforeupdate
:响应式数据更新时调用,发生在虚拟DOM打补丁之前,适合在更新之前访问现有的DOM,比如手动移除已添加的事件监听器updated
:虚拟 DOM 重新渲染和打补丁之后调用,组件DOM已经更新,可执行依赖于DOM的操作避免在这个钩子函数中操作数据,可能陷入死循环beforeDestroy
:实例销毁之前调用。这一步,实例仍然完全可用,this仍能获取到实例,常用于销毁定时器、解绑全局事件、销毁插件对象等操作
加分回答
父子组件钩子函数在生命周期的调用顺序:
- 渲染顺序:先父后子,完成顺序:先子后父
- 更新顺序:父更新导致子更新,子更新完成后父
- 销毁顺序:先父后子,完成顺序:先子后父
加载渲染过程
父beforeCreate
->
父created->
父beforeMount->
子beforeCreate->
子created->
子beforeMount- >
子mounted->
父mounted子组件更新过程
父beforeUpdate
->
子beforeUpdate->
子updated->
父updated父组件更新过程
父 beforeUpdate
->
父 updated销毁过程
父beforeDestroy
->
子beforeDestroy->
子destroyed->
父destroyed
生命周期是什么
Vue 实例有一个完整的生命周期,也就是从开始创建、初始化数据、编译模版、挂载Dom -> 渲染、更新 -> 渲染、卸载等一系列过程,我们称这是Vue的生命周期
各个生命周期的作用
生命周期 | 描述 |
---|---|
beforeCreate | 组件实例被创建之初,组件的属性生效之前 |
created | 组件实例已经完全创建,属性也绑定,但真实dom还没有生成,$el还不可用 |
beforeMount | 在挂载开始之前被调用:相关的 render 函数首次被调用 |
mounted | el 被新创建的 vm.$el 替换,并挂载到实例上去之后调用该钩子 |
beforeUpdate | 组件数据更新之前调用,发生在虚拟 DOM 打补丁之前 |
update | 组件数据更新之后 |
activated | keep-alive专属,组件被激活时调用 |
deactivated | keep-alive专属,组件被销毁时调用 |
beforeDestroy | 组件销毁前调用 |
destroyed | 组件销毁后调用 |
什么是vue生命周期?
- 答: Vue 实例从创建到销毁的过程,就是生命周期。从开始创建、初始化数据、编译模板、挂载Dom→渲染、更新→渲染、销毁等一系列过程,称之为 Vue 的生命周期。
vue生命周期的作用是什么?
- 答:它的生命周期中有多个事件钩子,让我们在控制整个Vue实例的过程时更容易形成好的逻辑。
vue生命周期总共有几个阶段?
- 答:它可以总共分为
8
个阶段:创建前/后、载入前/后、更新前/后、销毁前/销毁后。
第一次页面加载会触发哪几个钩子?
- 答:会触发下面这几个
beforeCreate
、created
、beforeMount
、mounted
。
DOM 渲染在哪个周期中就已经完成?
- 答:
DOM
渲染在mounted
中就已经完成了
❤ Vue 组件通信的方式
得分点 父子通信、自定义属性、
props
、$emit
、EventBus
、$on
、VueX
标准回答
Vue组件的通信方式分为两大类,一类是父子组件通信,另一类是任何关系类型组件通信(父子、兄弟、非兄弟)
- 父子组件的通信方式:
- 父给子传递数据,通过给子组件添加自定义属性,比如:list是父组件给子组件传递的数据。
- 子获取父的数据,在子组件中使用
props
属性获取,props是只读,不可以被修改,所有被修改都会失效和被警告 - 子给父传递数据,通过给子组件传递父组件的方法,子组件调用父组件的方法传递数据,比如:
deleteHandler
就是父组件的函数,在子组件中通过this.$emit('方法名',参数)
,调用父组件的方法,并把数据传递到父组件。
- 任何关系类型组件通信(父子、兄弟、非兄弟)方式: 自定义事件
EventBus
: 使用方法是创建一个新的Vue实例,需要通信的组件都引入该Vue实例,传递数据的组件使用event.$emit('名称',参数)
发送数据,接收数据的组件使用event.$on('名称',方法)
接收数据。
- VueX: 集中管理项目公共数据,Vuex 的状态存储是响应式的,当 Vue 组件从 store 中读取状态的时候,若 store 中的状态发生变化,那么相应的组件也会相应地得到高效更新。不能直接改变 store 中的状态。改变 store 中的状态的唯一途径就是显式地提交 (commit) mutation。
- 父子组件的通信方式:
加分回答
EventBus的优缺点
- 缺点:
- vue是单页应用,如果你在某一个页面刷新了之后,与之相关的EventBus会被移除,这样就导致业务走不下去。
- 同时如果页面中有反复操作的业务,EventBus在监听的时候就会触发很多次,需要好好处理EventBus在项目中的关系。
- 在vue页面销毁时,同时移除EventBus事件监听。
- 优点:
- 解决了多层组件之间繁琐的事件传播,使用原理十分简单,代码量少。
- 适合于简单,组件传递数据较少的项目,大型项目业务复杂的还是尽量使用VueX
- 缺点:
❤ Vue 组件渲染和更新的过程
- 初次渲染过程
- 解析模板为render函数(或在开发环境已完成,vue-loader)
- 触发响应式,监听 data 属性 getter setter
- 执行render函数,生成vnode,patch(elem,vnode)
- 注意点 :执行 render 函数会触发 getter
- 更新过程
- 修改data,触发setter(此前在getter中已被监听)
- 重新执行render 函数,生成newVnode
- patch(vnode,newVnode)
- 异步渲染
- 回顾$nextTick
- 汇总data的修改,一次性更新视图
- 减少DOM操作次数,提高性能
❤ Vue2 双向绑定的原理与缺陷
- 得分点:
Object.defineProperty
、getter
、setter
- 标准回答: Vue响应式指的是:组件的data发生变化,立刻触发视图的更新
- 原理: Vue 采用数据劫持结合发布者-订阅者模式的方式来实现数据的响应式,通过
Object.defineProperty()
来劫持数据的setter
,getter
,在数据变动时发布消息给订阅者,订阅者收到消息后进行相应的处理。 通过原生 js 提供的监听数据的API,当数据发生变化的时候,在回调函数中修改dom - 核心API:
Object.defineProperty()
API 的使用- 作用:用来定义对象属性
- 特点: 默认情况下定义的数据的属性不能修改;描述属性和存取属性不能同时使用,使用会报错
- 响应式原理: 获取属性值会触发
getter
方法;设置属性值会触发setter
方法 在setter
方法中调用修改dom的方法
- 原理: Vue 采用数据劫持结合发布者-订阅者模式的方式来实现数据的响应式,通过
- 加分回答:
Object.defineProperty
的缺点- 一次性递归到底开销很大,如果数据很大,大量的递归导致调用栈溢出
- 不能监听对象的新增属性和删除属性
- 无法正确的监听数组的方法,当监听的下标对应的数据发生改变时
代码示例用到 observe-demo 项目的 observe.js
1 | // 触发更新视图 |
❤ Vue3 实现数据双向绑定的方法
- 得分点 Proxy、数据拦截、劫持整个对象、返回一个新对象、有13种劫持
- 标准回答 Vue3.0 是通过Proxy实现的数据双向绑定,Proxy是ES6中新增的一个特性,实现的过程是在目标对象之前设置了一层“拦截”,外界对该对象的访问,都必须先通过这层拦截,因此提供了一种机制,可以对外界的访问进行过滤和改写。
- 用法: ES6 原生提供 Proxy 构造函数,用来生成 Proxy 实例。 var proxy = new Proxy(target, handler) target: 是用Proxy包装的被代理对象(可以是任何类型的对象,包括原生数组,函数,甚至另一个代理)。
- handler: 是一个对象,其声明了代理target 的一些操作,其属性是当执行一个操作时定义代理的行为的函数。
- 加分回答
Object.defineProperty
的问题:在Vue中,Object.defineProperty
无法监控到数组下标的变化,导致直接通过数组的下标给数组设置值,不能实时响应。目前只针对以上方法做了hack处理,所以数组属性是检测不到的,有局限性Object.defineProperty只能劫持对象的属性,因此我们需要对每个对象的每个属性进行遍历。Vue里,是通过递归以及遍历data对象来实现对数据的监控的,如果属性值也是对象那么需要深度遍历,显然如果能劫持一个完整的对象,不管是对操作性还是性能都会有一个很大的提升。- Proxy的两个优点:可以劫持整个对象,并返回一个新对象,有13种劫持
❤ Vuex 是什么,每个属性是干嘛的,如何使用 ?
- 得分点:state、mutations、getters、actions、module、store.commit、store.dispatch
- 标准回答: Vuex是集中管理项目公共数据的。
- Vuex 有state、mutations 、getters、actions、module属性。
- state 属性用来存储公共管理的数据。
- mutations 属性定义改变state中数据的方法, 注意:不要在mutation中的方法中写异步方法ajax,那样数据就不可跟踪了 。
- getters 属性可以认为是定义 store 的计算属性。就像计算属性一样,getter 的返回值会根据它的依赖被缓存起来,且只有当它的依赖值发生了改变才会被重新计算。
- action属性类似于 mutation,不同在于:Action 提交的是 mutation,而不是直接变更状态。Action 可以包含任意异步操作。
- moudle属性是将store分割成模块。每个模块拥有自己的 state、mutation、action、getter、甚至是嵌套子模块,从上至下进行同样方式的分割
- 使用方法: state :直接以对象方式添加属性 mutations :通过
store.commit
调用 action:通过store.dispatch
方法触发 getters:直接通过store.getters.调用
- 使用方法: state :直接以对象方式添加属性 mutations :通过
- 加分回答: 可以使用mapState、mapMutations、mapAction、mapGetters一次性获取每个属性下对应的多个方法。 VueX在大型项目中比较常用,非关系组件传递数据比较方便。
请简单实现双向数据绑定mvvm
1 | <input id="input"/> |
❤ HashRouter 和 HistoryRouter的区别和原理?
- 得分点
#
window.onhashchange
history.pushState
window.onpopstate
- 标准回答
HashRouter
和HistoryRouter
的区别:- history 和 hash都是利用浏览器的两种特性实现前端路由,history是利用浏览历史记录栈的API实现,hash是监听location对象hash值变化事件来实现
- history的url没有’#’号,hash反之
- 相同的url,history会触发添加到浏览器历史记录栈中,hash不会触发,history需要后端配合,如果后端不配合刷新新页面会出现404,hash不需要。
- HashRouter的原理:通过
window.onhashchange
方法获取新URL中hash值,再做进一步处理 - HistoryRouter的原理:通过
history.pushState
使用它做页面跳转不会触发页面刷新,使用window.onpopstate
监听浏览器的前进和后退,再做其他处理
- 加分回答
- hash模式下 url 会带有#,需要url更优雅时,可以使用history模式。 需要兼容低版本的浏览器时,建议使用hash模式。 需要添加任意类型数据到记录时,可以使用history模式。
- 两者优势:
- to B 的系统推荐用 hash,简单易用,对url规范不敏感 (后台管理页面)
- to C 的系统,可以考虑选择 H5 history,但需要服务端支持
- 能选择简单的,就别用复杂的,要考虑成本和收益
❤ Diff 算法
- 得分点 patch、patchVnode、updateChildren(key重要性)、vue优化时间复杂度为O(n)
- 标准回答
- Diff算法比较过程
- 第一步:patch函数中对新老节点进行比较 如果新节点不存在就销毁老节点 如果老节点不存在,直接创建新的节点 当两个节点是相同节点的时候,进入 patchVnode 的过程,比较两个节点的内部
- 第二步:patchVnode函数比较两个虚拟节点内部 如果两个虚拟节点完全相同,返回 当前vnode 的children 不是textNode,再分成三种情况
- 有新children,没有旧children,创建新的
- 没有新children,有旧children,删除旧的
- 新children、旧children都有
- 执行
updateChildren
比较children的差异,这里就是diff算法的核心 当前vnode 的children 是textNode,直接更新text
- 第三步:updateChildren函数子节点进行比较
- 第一步 头头比较。若相似,旧头新头指针后移(即
oldStartIdx++
&&newStartIdx++
),真实dom不变,进入下一次循环;不相似,进入第二步。 - 第二步 尾尾比较。若相似,旧尾新尾指针前移(即
oldEndIdx--
&&newEndIdx--
),真实dom不变,进入下一次循环;不相似,进入第三步。 - 第三步 头尾比较。若相似,旧头指针后移,新尾指针前移(即
oldStartIdx++
&&newEndIdx--
),未确认dom序列中的头移到尾,进入下一次循环;不相似,进入第四步。 - 第四步 尾头比较。若相似,旧尾指针前移,新头指针后移(即
oldEndIdx--
&&newStartIdx++
),未确认dom序列中的尾移到头,进入下一次循环;不相似,进入第五步。 - 第五步 若节点有key且在旧子节点数组中找到sameVnode(tag和key都一致),则将其dom移动到当前真实dom序列的头部,新头指针后移(即
newStartIdx++
);否则,vnode对应的dom(vnode[newStartIdx].elm
)插入当前真实dom序列的头部,新头指针后移(即newStartIdx++
)。 - 但结束循环后,有两种情况需要考虑:
- 新的字节点数组(newCh)被遍历完(
newStartIdx > newEndIdx
)。那就需要把多余的旧dom(oldStartIdx -> oldEndIdx
)都删除,上述例子中就是c,d
; - 新的字节点数组(oldCh)被遍历完(
oldStartIdx > oldEndIdx
)。那就需要把多余的新dom(newStartIdx -> newEndIdx
)都添加。
- 新的字节点数组(newCh)被遍历完(
- 第一步 头头比较。若相似,旧头新头指针后移(即
❤ Vue 的 keep-alive
- 得分点 缓存组件、条件缓存、路由配合条件缓存、不重新加载、activated、deactivated
- 标准回答
<keep-alive>
作用:缓存组件,提升性能,避免重复加载一些不需要经常变动且内容较多的组件。<keep-alive>
的使用方法:使用<keep-alive>
标签对需要缓存的组件进行包裹,默认情况下被<keep-alive>
标签包裹的组件都会进行缓存,区分被包裹的组件是否缓存有两种方法,- 第一种是给keepalive 添加属性,组件名称指的是具体组件添加的name,不是路由里面的name。include 包含的组件(可以为字符串,数组,以及正则表达式,只有匹配的组件会被缓存)。exclude 排除的组件(以为字符串,数组,以及正则表达式,任何匹配的组件都不会被缓存)。
- 第二种也是最常用的一种是,和路由配合使用:在路由中添加meta属性。 使用keepalive导致组件不重新加载,也就不会重新执行生命周期的函数,如果要解决这个问题,就需要两个属性进入时触发:activated 退出时触发:deactivated
- 加分回答
<keep-alive>
适用的场景:首页展示固定数据的组件,比如banner九宫格
❤ computed 和 watch 的区别
- 得分点 computed值有缓存、触发条件是依赖值发生更改、 watch无缓存支持异步、监听数据变化
- 标准回答
- computed: 是计算属性,依赖其它属性值,并且 computed 的值有缓存,只有它依赖的属性值发生改变,下一次获取 computed 的值时才会重新计算 computed 的值;
- watch: 更多的是观察的作用,支持异步,类似于某些数据的监听回调 ,每当监听的数据变化时都会执行回调进行后续操作;
- 加分回答
- computed应用场景:需要进行数值计算,并且依赖于其它数据时,应该使用 computed,因为可以利用 computed 的缓存特性,避免每次获取值时,都要重新计算;
- watch应用场景:需要在数据变化时执行异步或开销较大的操作时,应该使用 watch,使用 watch 选项允许我们执行异步操作 ( 访问一个 API ),限制我们执行该操作的频率,并在我们得到最终结果前,设置中间状态。这些都是计算属性无法做到的。
❤ Vue 中 $nextTick 作用与原理
- 得分点 异步渲染、获取DOM、Promise
- 标准回答 Vue 在更新 DOM 时是异步执行的,在修改数据后,视图不会立刻更新,而是等同一事件循环中的所有数据变化完成之后,再统一进行视图更新。所以修改完数据,立即在方法中获取DOM,获取的仍然是未修改的DOM。
$nextTick
的作用是:该方法中的代码会在当前渲染完成后执行,就解决了异步渲染获取不到更新后DOM的问题了。$nextTick
的原理:$nextTick
本质是返回一个Promise
- 加分回答
- 应用场景:在钩子函数created()里面想要获取操作Dom,把操作DOM的方法放在
$nextTick
中
- 应用场景:在钩子函数created()里面想要获取操作Dom,把操作DOM的方法放在
❤ vue-router 实现懒加载的方法
得分点 import、require
标准回答
vue-router 实现懒加载的方法有两种:
ES6 的 impot方式:
1
2
3
4component: () => import(
/* webpackChunkName: "about" */
'../views/About.vue'
)VUE 中的异步组件进行懒加载方式:
1
2
3component: resolve=>(
require(['../views/About'],resolve)
)
加分回答
- vue-router 实现懒加载的作用:性能优化,不用到该路由,不加载该组件。
vue、react、angular
Vue.js
一个用于创建web
交互界面的库,是一个精简的MVVM
。它通过双向数据绑定把View
层和Model
层连接了起来。实际的DOM
封装和输出格式都被抽象为了Directives
和Filters
AngularJS
是一个比较完善的前端MVVM
框架,包含模板,数据双向绑定,路由,模块化,服务,依赖注入等所有功能,模板功能强大丰富,自带了丰富的Angular
指令react
React
仅仅是VIEW
层是facebook
公司。推出的一个用于构建UI
的一个库,能够实现服务器端的渲染。用了virtual dom
,所以性能很好。
未解决的问题:
如何设计一个组件
我用vue开发的所有项目,都是采用组件化的思想开发的。一般我在搭建项目的时候,会创建一个views目录和一个commen目录和一个feature目录,views目录中放页面级的组件,commen中放公共组件(如:head(公共头组件),foot(公共底部组件)等),feature目录内放功能组件(如:swiper(轮播功能组件),tabbar(切换功能组件)、list(上拉加载更多功能组件))
首先,组件可以提升整个项目的开发效率。能够把页面抽象成多个相对独立的模块,解决了我们传统项目开发:效率低、难维护、复用性低等问题。
使用Vue.extend
方法创建一个组件,然后使用Vue.component
方法注册组件。但是我们一般用脚手架开发项目,每个 .vue单文件就是一个组件。在另一组件import 导入,并在components
中注册,子组件需要数据,可以在props
中接受定义。而子组件修改好数据后,想把数据传递给父组件。可以采用$emit
方法。
Vue 面试真题演练
- 自己觉得是面试重点
- 网上收集整理的面试题
- 热门技术和知识点
Vue3
面试题
- Vue3比Vue2有什么优势?
- 描述Vue3生命周期
- 如何看待Composition API和Options API?
- 如何理解 ref toRef 和 toRefs?
- Vue3升级了哪些重要的功能?
- Composition API如何实现代码逻辑复用?
- Vue3如何实现响应式?
- watch和watchEffect的区别是什么?
- setup中如何获取组件实例?
- Vue3为何比Vue2快?
- Vite是什么?
- Composition API和React Hooks的对比
Vue3 比 Vue2 有什么优势?
- 性能更好
- 体积更小
- 更好的ts支持
- 更好的代码组织
- 更好的逻辑抽离
- 更多新功能
Vue2 和 Vue3有什么区别
- 重构响应式系统,使用
Proxy
替换Object.defineProperty
,使用Proxy
优势:
可直接监听数组类型的数据变化
监听的目标为对象本身,不需要像
Object.defineProperty
一样遍历每个属性,有一定的性能提升可拦截
apply、ownKeys、has
等13种方法,而Object.defineProperty
不行直接实现对象属性的新增/删除
新增
Composition API
,更好的逻辑复用和代码组织重构
Virtual DOM
- 模板编译时的优化,将一些静态节点编译成常量
slot
优化,将slot
编译为lazy
函数,将slot
的渲染的决定权交给子组件- 模板中内联事件的提取并重用(原本每次渲染都重新生成内联函数)
代码结构调整,更便于Tree shaking,使得体积更小
使用Typescript替换Flow
vue3 带来的新特性/亮点
参考链接
1. 压缩包体积更小
当前最小化并被压缩的 Vue 运行时大小约为 20kB(2.6.10 版为 22.8kB)。Vue 3.0捆绑包的大小大约会减少一半,即只有10kB!
2. Object.defineProperty -> Proxy
Object.defineProperty
是一个相对比较昂贵的操作,因为它直接操作对象的属性,颗粒度比较小。将它替换为es6的Proxy
,在目标对象之上架了一层拦截,代理的是对象而不是对象的属性。这样可以将原本对对象属性的操作变为对整个对象的操作,颗粒度变大。- javascript引擎在解析的时候希望对象的结构越稳定越好,如果对象一直在变,可优化性降低,proxy不需要对原始对象做太多操作。
3. Virtual DOM 重构
vdom的本质是一个抽象层,用javascript描述界面渲染成什么样子。react用jsx,没办法检测出可以优化的动态代码,所以做时间分片,vue中足够快的话可以不用时间分片
4. Performance
vue3在性能方面比vue2快了2倍。
- 重写了虚拟DOM的实现
- 运行时编译
- update性能提高
- SSR速度提高
5. Tree-shaking support
vue3中的核心api都支持了tree-shaking,这些api都是通过包引入的方式而不是直接在实例化时就注入,只会对使用到的功能或特性进行打包(按需打包),这意味着更多的功能和更小的体积。
6. Composition API
vue2中,我们一般会采用mixin来复用逻辑代码,用倒是挺好用的,不过也存在一些问题:例如代码来源不清晰、方法属性等冲突。基于此在vue3中引入了Composition API(组合API),使用纯函数分隔复用代码。和React中的
hooks
的概念很相似
- 更好的逻辑复用和代码组织
- 更好的类型推导
7. 新增的三个组件Fragment、Teleport、Suspense
8. Better TypeScript support
在vue2中使用过TypesScript的童鞋应该有过体会,写起来实在是有点难受。vue3则是使用ts进行了重写,开发者使用vue3时拥有更好的类型支持和更好的编写体验。
Vue3 生命周期
- Options API 生命周期
- beforeDestroy 改为 beforeUnmount
- destroyed 改为 unmouted
- 其他沿用 Vue2 的生命周期
- Composition API 生命周期
代码示例用到 vue3-demo 项目的 life-cycles 组件
Composition API 对比 Options API
Composition API 带来了什么?
更好的代码组织
更好的逻辑复用(有一道专门的面试题)
更好的类型推导
Composition API 和 Options API 如何选择?
- 不建议共用,会引起混乱
- 小型项目、业务逻辑简单,用 Options API
- 中大型项目、逻辑复杂,用 Composition API
别误解 Composition API
- Composition API 属于高阶技巧,不是基础必会
- Composition API 是为解决复杂业务逻辑而设计
- Composition API 就像 Hooks 在 React 中的地位
为何需要 toRef 和 toRefs?
- 初衷:不丢失响应式的情况下,把对象数据分解/扩散
- 前提:针对的是响应式对象( reactive 封装的)非普通对象
- 注意:不创造响应式,而是延续响应式
如何理解 ref toRef 和 toRefs
- ref toRef toRefs 是什么
- 最佳使用方式
- 进阶,深入理解
ref
- 生成值类型的响应式数据
- 可用于模板和 reactive
- 通过 .value 修改值
代码示例用到 vue3-demo 项目的 Ref 组件
toRef
- 针对一个响应式对象( reactive 封装)的 prop
- 创建一个 ref,具有响应式
- 两者保持引用关系
代码示例用到 vue3-demo 项目的 ToRef 组件
toRefs
- 将响应式对象(reactive封装)转换为普通对象
- 对象的每个prop都是对应的ref
- 两者保持引用关系
代码示例用到 vue3-demo 项目的 ToRefs 组件
合成函数返回响应式对象
最佳使用方式
- 用 reactive 做对象的响应式,用 ref 做值类型响应式
- setup 中返回 toRefs(state),或者 toRef(state,‘xxx’)
- ref 的变量命名都用 xxxRef
- 合成函数返回响应式对象时,使用 toRefs
进阶,深入理解
- 为何需要 ref ?
- 返回值类型,会丢失响应式
- 如在 setup、computed、合成函数,都有可能返回值类型
- Vue 如不定义 ref,用户将自造 ref,反而混乱
- 为何需要 .value ?
- ref 是一个对象(不丢失响应式),value 存储值
- 通过 .value 属性的 get 和 set 实现响应式
- 用于模板、reactive 时,不需要 .value,其他情况都需要
- 为何需要 toRef to Refs ?
- 初衷:不丢失响应式的情况下,把对象数据分解/扩散
- 前提:针对的是响应式对象(reactive封装的)非普通对象
- 注意:不创造响应式,而是延续响应式
Vue3 升级了哪些重要的功能
- createApp
- emits 属性
- 生命周期
- 多事件处理

- Fragment

- 移除 .sync

- 异步组件的写法
- 移除 filter
- Teleport
- Suspense

- Composition API
- reactive
- ref 相关
- readonly
- watch 和 watchEffect
- setup
- 生命周期钩子函数
Composition API 如何实现代码逻辑复用?
- 抽离逻辑代码到一个函数
- 函数命名约定为 useXxxx 格式( React Hooks 也是)
- 在 setup 中引用 useXxx 函数
代码示例用到 vue3-demo 项目的 MousePosition 组件
Vue3 如何实现响应式?
- 回顾 Vue2.x 的 Object.defineProperty
- 学习 Proxy 语法
- Vue3 如何用 Proxy 实现响应式
Proxy 实现响应式
- 回顾 Object.defineProperty
- Object.defineProperty 的缺点
- 深度监听需要一次性递归
- 无法监听新增属性/删除属性 ( Vue.set Vue.delete )
- 无法原生监听数组,需要特殊处理
- Object.defineProperty 的缺点
- 深度监听影响性能
- 数组监听需要编写代码
- 监听不到属性的新增和删除属性
代码示例用到 observe-demo 项目的 observe.js
Proxy 实现响应式
基本使用
代码示例用到 observe-demo 项目的 proxy-demo.js
Reflect
- 和 Proxy 能力一一对应
- 规范化、标准化、函数式
- 替代掉 Object 上的工具函数
实现响应式
- 深度监听,性能更好
- 可监听新增/删除属性
- 可监听数组变化
代码示例用到 observe-demo 项目的 proxy-observe.js
两者对比
总结
- Proxy 能规避 Object.defineProperty 的问题
- Proxy 无法兼容所有浏览器,无法 polyfill
v-model 和 .sync 修饰符 的对比
Vue2.3.0+新增的,用于双向绑定的,由于绑定代码的复杂而实现的语法糖
代码示例用到 vue3-demo 项目的 VModel 组件
watch 和 watchEffect 的区别
- 两者都可监听 data 属性变化
- watch 需要明确监听哪个属性
- watchEffect 会根据其中的属性,自动监听其变化
代码示例用到 vue3-demo 项目的 Watch 组件
setup 中如何获取组件实例?
- 在 setup 和其他 Composition API 中没有 this
- 可通过 getCurrentlnstance 获取当前实例
- 若使用 Options API 可照常使用 this
代码示例用到 vue3-demo 项目的 GetInstance 组件
Vue3 为何比 Vue2 快?
- Proxy 响应式
- PatchFlag
- hoistStatic
- cacheHandler
- SSR 优化
- tree-shaking
详细讲解:
编译模板网站:https://vue-next-template-explorer.netlify.app/
Proxy 响应式
- 上面提过,Proxy 能规避 Object.defineProperty 的问题
PatchFlag 静态标记
- 编译模板时,动态节点做标记
- 标记,分为不同的类型,如 TEXT PROPS
- diff 算法时,可以区分静态节点,以及不同类型的动态节点
hoistStatic 静态提升
- 将静态节点的定义,提升到父作用域,缓存起来
- 多个相邻的静态节点,会被合并起来
- 典型的拿空间换时间的优化策略
cacheHandler 缓存事件
SSR 优化
- 静态节点直接输出,绕过了 vdom
- 动态节点,还是需要动态渲染
tree-shaking 优化
- 编译时,根据不同的情况,引入不同的 API
Vite 是什么?
- 一个前端打包工具,Vue 作者发起的项目
- 借助 Vue 的影响力,发展较快,和 webpack 竞争
- 优势:开发环境下无需打包,启动快
Vite 为何启动快?
- 开发环境使用 ES6Module,无需打包——非常快
- 生产环境使用 rollup,并不会快很多
代码示例用到 es-module-demo 项目
Composition API 和 React Hooks 的对比
- 前者 setup 只会被调用一次,而后者函数会被多次调用
- 前者无需 useMemo useCallback,因为 setup 只调用一次
- 前者无需顾虑调用顺序,而后者需要保证 hooks 的顺序一致
- 前者 reactive + ref 比后者 useState,要难理解