目录
前言
Vue 的优缺点
SPA的理解
MVVM的理解
单向数据流的理解
响应式原理
手写观察者模式
手写发布订阅模式
组件中的data为什么是一个函数
生命周期
父组件与子组件生命周期钩子执行顺序
父组件监听子组件生命周期钩子的方式
组件通信方式
v-on监听多个方法
常用修饰符
class、style的动态绑定方式
v-show与v-if的区别
为什么v-if不能和v-for一起使用
computed与watch的区别与使用场景
slot插槽的理解与使用
Vue.$delete和delete的区别
Vue.$set如何解决对象新增属性不能响应的问题
Vue.$nextTick的原理
虚拟DOM的理解
Diff算法原理
key的作用
动态组件 和 异步组件
Vue.directive 有写过么,有哪些应用场景
Vue 过滤器
关于mixin的理解,有什么应用场景
介绍一下keep-alive
Vue-Router 配置 404 页面
Vue-Router 有哪几种导航守卫
Vue-Router 完整的导航解析流程
Vuex 的理解及使用
Vuex 刷新后数据丢失怎么办
Vuex 如何知道 State 是通过 Mutation 修改还是外部修改
Vue SSR 了解么
Vue2 与 Vue3 的区别 ?Vue3有哪些优化点?
Vue 性能优化
本文汇总了vue常用知识点与常见面试题,附上本人对该vue知识介绍相关博客,适合收藏,经常回顾,也许每次阅读都会有进一步理解。
优点
1、创建单页面应用的轻量级Web应用框架
2、简单易用
3、双向数据绑定
4、组件化的思想
5、虚拟DOM
6、数据驱动视图
7、前后端分离
缺点
1、Vue在开发多页应时不够灵活,需要配置多入口
2、不支持IE8
SPA是 Single-Page-Application
的缩写,翻译过来就是单页应用。在WEB页面初始化时一同加载Html、Javascript、Css。一旦页面加载完成,SPA不会因为用户操作而进行页面重新加载或跳转,取而代之的是利用路由机制实现Html内容的变换。
优点
1、良好的用户体验,内容更改无需重载页面
2、SPA相对服务端压力更小
3、前后端职责分离,架构清晰
缺点
1、由于前端渲染,搜索引擎不会解析JS,只能抓取首页未渲染的模板,不利于SEO
2、单页面应用,在加载页面的时候将JavaScript、CSS统一加载,所以首次加载耗时更多
3、单页面应用需在一个页面显示所有的内容,默认不支持浏览器的前进后退(前端路由机制解决了该窘境,Hash模式中Hash变化会被浏览器记录,History模式利用 H5 新增的pushState
和replaceState
方法可改变浏览器历史记录栈)
MVVM是Model-View-ViewModel
的缩写。Model 代表数据层,可定义修改数据、编写业务逻辑。View 代表视图层,负责将数据渲染成页面。ViewModel 负责监听数据层数据变化,控制视图层行为交互,简单讲,就是同步数据层和视图层的对象。ViewModel 通过双向绑定把 View 和 Model 层连接起来,且同步工作无需人为干涉,使开发人员只关注业务逻辑,无需频繁操作DOM,不需关注数据状态的同步问题。
具体细节参考文章
面试官:你了解MVVM框架吗?(Vue MVVM详细介绍,一看就会)_czjl6886的博客-CSDN博客随着前端的发展,MVVM思想越来越受到大家的欢迎,那么MVVM到底是什么呢?下面,我将简要介绍MVVM的思想,并从Vue的角度,分析具体代码中是怎么实现这种思想的。https://blog.csdn.net/czjl6886/article/details/121717212?spm=1001.2014.3001.5502
我们经常说 Vue 的双向绑定,其实是在单向绑定的基础上给元素添加 input/change
事件,来动态修改视图。Vue 组件间传递数据仍然是单项的,即父组件传递到子组件。子组件内部可以定义依赖 props 中的值,但无权修改父组件传递的数据,这样做防止子组件意外变更父组件的状态,导致应用数据流向难以理解。
如果在子组件内部直接
更改prop,会遇到警告处理。
以下为2种定义依赖props中的值
1、通过 data 定义属性并将 prop 作为初始值
2、 用 computed 计算属性去定义依赖 prop 的值
//子组件
2、@hook
// 父组件
//子组件
父子组件
1、props、$emit
2、$parent、$children
跨级组件
1、$attrs、$listeners
2、provide、inject
父子、跨级、兄弟
1、EventBus(事件总线)
2、Vuex(状态管理)
扩展
1、缓存
2、路由
具体细节参考文章
Vue组件间的通信方式(多种场景,通俗易懂,建议收藏)_前端不释卷leo的博客-CSDN博客_vue组件间通信以下是我在工作中用到过的vue组件之间的通信方式,不同的场景使用不同的方式,基本满足所有开发场景中的通信需求,话不多说直接开始,满满的干货,建议收藏。1、父子组件之间的通信父 >>> 子 (Props)一个组件里面引入另外一个组件,此时构成了一种“父子关系”,当前组件为“父”,引入的组件为“子”,如当前组件(父),在父组件中通过 “:message” 向子组件通信。 表单修饰符 1、lazy::失去焦点后同步信息 2、trim:自动过滤首尾空格 3、number:输入值转为数值类型 事件修饰符 1、stop:阻止冒泡(js中stopPropagation) 2、prevent:阻止默认行为(js中preventDefault) 3、self:仅绑定元素自身触发 4、once:只触发一次 对象方式 数组方式 相同点 都是控制页面元素的显示与隐藏 不同点 1、v-show 控制的是元素的CSS(v-show="false"相当于display:none);v-if 是控制元素本身的添加或删除(即是否出现在DOM结构中) 2、v-show 由 false 变为 true 的时候不会触发组件的生命周期。v-if 由 false 变为 true 则会触发组件的 3、频繁切换时,使用v-show;不频繁切换、加快页面渲染、需要销毁元素使用v-if 性能浪费,每次渲染都要先循环再进行条件判断,考虑用计算属性替代。 Vue2.x中 Vue3.x中 computed 计算属性,依赖其他属性值,且值具备缓存的特性。只有它依赖的属性值发生改变,下一次获取的值才会重新计算。 适用于数值计算,并且依赖于其他属性时。因为可以利用缓存特性,避免每次获取值,都需要重新计算。 watch 观察属性,监听属性值变动。每当属性值发生变化,都会执行相应的回调。 适用于数据变化时执行异步或开销比较大的操作。 具体细节参考文章 上手Vue:深度理解computed、watch及其区别_czjl6886的博客-CSDN博客_vue中computed和watch的区别computed(计算属性)与watch(侦听器),是Vue中常用的属性,那么什么时候该如何computed,什么时候该使用watch呢?https://blog.csdn.net/czjl6886/article/details/121454266?spm=1001.2014.3001.5502 slot 插槽,可以理解为 具体细节参考文章 Vue 插槽(slot)详细介绍(对比版本变化,避免踩坑)_前端不释卷leo的博客-CSDN博客Vue中的插槽(slot)在项目中用的也是比较多的,今天就来介绍一下插槽的基本使用以及Vue版本更新之后的插槽用法变化。插槽是什么?插槽就是子组件中的提供给父组件使用的一个占位符,用 Vue.$delete 是直接删除了元素,改变了数组的长度;delete 是将被删除的元素变成内 Vue.$set的出现是由于 源码位置:vue/src/core/observer/index.js 具体原理 1、若是数组,直接使用数组的 splice 方法触发响应式 2、若是对象,判断属性是否存在,对象是否是响应式 3、以上都不满足,最后通过 defineReactive 对属性进行响应式处理 具体细节参考文章 Vue 全局API 详细介绍(nextTick、set、delete、......)_前端不释卷leo的博客-CSDN博客Tips:Vue全局(内置)API,在实例中对应使用方式this.$apiName。Vue.extend(options)使用基础 Vue 构造器,创建一个“子类”。参数是一个包含组件选项的对象。data选项是特例,需要注意 - 在Vue.extend()中它必须是函数。// 创建构造器var Greet = Vue.extend({ template: ' {{firstName}} {{lastName}} say {{alias}} nextTick:在下次 DOM 更新循环结束之后执行延迟回调。常用于修改数据后获取更新后的DOM。 源码位置:vue/src/core/util/next-tick.js 总结: 1、运用异步锁的概念,保证同一时刻任务队列中只有一个 flushCallbacks。当 pengding 为 false 的时候,表示浏览器任务队列中没有 flushCallbacks 函数;当 pengding 为 true 的时候,表示浏览器任务队列中已经放入 flushCallbacks;待执行 flushCallback 函数时,pengding 会被再次置为 false,表示下一个 flushCallbacks 可进入任务队列 2、环境能力检测,选择可选中效率最高的(宏任务/微任务)进行包装执行,保证是在同步代码都执行完成后再去执行修改 DOM 等操作 3、flushCallbacks 先拷贝再清空,为了防止nextTick嵌套nextTick导致循环不结束 具体细节参考如上文章 虚拟 DOM 的出现解决了浏览器的性能问题。虚拟 DOM 是一个用 JS 模拟的 DOM 结构对象(Vnode),用于频繁更改 DOM 操作后不立即更新 DOM,而是对比新老 Vnode,更新获取最新的Vnode,最后再一次性映射成真实的 DOM。这样做的原因是操作内存中操作 JS 对象速度比操作 DOM 快很多。 举个例子 real dom 用 JS 来模拟以上 DOM 节点实现虚拟 DOM 虚拟 DOM 转为真实的节点 具体细节参考文章 Vue中的Diff算法_Hayden-CSDN博客_diff算法Vue中的Diff算法本篇文章主要介绍Diff算法的思想和Vue中对Diff算法的基本实现。1. 为什么要用Diff算法由于在浏览器中操作DOM的代价是非常“昂贵”的,所以才在Vue引入了Virtual DOM,Virtual DOM是对真实DOM的一种抽象描述,不懂的朋友可以自行查阅相关资料。即使使用了Virtual DOM来进行真实DOM的渲染,在页面更新的时候,也不能全量地将整颗Vi...https://blog.csdn.net/qq_34179086/article/details/88086427?ops_request_misc=&request_id=&biz_id=102&utm_term=vue%20diff%E5%8E%9F%E7%90%86&utm_medium=distribute.pc_search_result.none-task-blog-2~all~sobaiduweb~default-8-88086427.pc_search_mgc_flag&spm=1018.2226.3001.4187 key 是 Vue 中 vnode 的唯一标记,我们的 diff 的算法中 sameVnode 和 updateChildren 中就使用到了 key。 sameVnode 用来判断是否为同一节点。常见的业务场景是一个列表,若 key 值是列表索引,在新增或删除的情况下会存在就地复用的问题。(简单说,复用了上一个在当前位置元素的状态)所以 key 值的唯一,确保 diff 更准确。 updateChildren 中当其中四种假设都未匹配,就需要依赖老节点的 key 和 索引创建关系映射表,再用新节点的 key 去关系映射表去寻找索引进行更新,这保证 diff 算法更加快速。 简单来说就是:根据key判断是否复用,实现高效渲染 动态组件通过 具体细节参考官网 动态组件 & 异步组件 — Vue.jsVue.js - The Progressive JavaScript Frameworkhttps://cn.vuejs.org/v2/guide/components-dynamic-async.html Vue.directive 可以注册全局指令和局部指令。 指令定义函数提供如下钩子函数 1、bind:指令第一次绑定到元素时调用(只调用一次) 2、inserted: 被绑定元素插入父节点时使用(父节点存在即可调用) 3、update:被绑定元素所在模板更新时调用,不论绑定值是否变化。通过比较更新前后的绑定值 4、 componentUpdated: 被绑定元素所在模板完成一次更新周期时调用 5、unbind: 只调用一次,指令与元素解绑时调用 具体细节参考官网 自定义指令 — Vue.jsVue.js - The Progressive JavaScript Frameworkhttps://cn.vuejs.org/v2/guide/custom-directive.html Vue 过滤器可用在两个地方:双花括号插值和 v-bind 表达式。 Vue3 中已经废弃这个特点。 过滤器分为 全局过滤器 和 局部过滤器。 局部过滤器 全局过滤器 过滤器可串联,执行顺序从左到右,第二个过滤器输入值是第一个过滤器的输出值 mixin 混入分全局混入和局部混入,本质是 JS 对象,如 data、components、computed、methods 等。 全局混入不推荐使用,会影响后续每个Vue实例的创建。局部混入可提取组件间相同的代码,进行逻辑复用。 适用场景:如多个页面具备 具体细节参考文章 Vue 混入(mixin)详细介绍(可复用性、全局混入)_前端不释卷leo的博客-CSDN博客基础混入(mixin)提供了一种非常灵活的方式,来分发 Vue 组件中的可复用功能。一个混入对象可以包含任意组件选项(如data、methods、mounted等等)。当组件使用混入对象时,所有混入对象的选项将被“混合”进入该组件本身的选项。基本使用1、定义一个混入(mixin)let mixin = { created() { console.log("我是mixin里面的created!") }, methods: { hello() { .https://blog.csdn.net/qq_41809113/article/details/121912330?spm=1001.2014.3001.5502 具体细节参考文章 Vue keep-alive 详细介绍(动态组件、路由组件缓存优化)_前端不释卷leo的博客-CSDN博客用法keep-alive 是Vue的内置组件,当它包裹动态组件时,会缓存不活动的组件实例,而不是销毁它们。和 transition 相似,keep-alive 是一个抽象组件:它自身不会渲染成一个 DOM 元素,也不会出现在父组件链中。当组件在 * 代表通配符,若放在任意路由前,会被先匹配,导致跳转到 404 页面,所以需将如下配置置于最后。 全局路由守卫 在路由跳转前触发,可在执行 next 方法前做一些身份登录验证的逻辑 全局后置钩子 和守卫不同的是,这些钩子不会接受 路由独享守卫 可在路由配置上直接定义 beforeEnter 组件守卫 组件内可直接定义如下路由导航守卫 更多细节参考官网 API 参考 | Vue Routerhttps://router.vuejs.org/zh/api/#router-link 1、导航被触发 2、在失活的组件里调用 beforeRouteLeave 守卫 3、调用全局 beforeEach 前置守卫 4、重用的组件调用 beforeRouteUpdate 守卫(2.2+) 5、路由配置调用 beforeEnter 6、解析异步路由组件 7、在被激活的组件里调用 beforeRouteEnter 守卫 8、调用全局的 beforeResolve 守卫(2.5+) 9、导航被确认 10、调用全局的 afterEach 11、触发 DOM 更新 12、调用 beforeRouteEnter 守卫中传给 next 的回调函数,创建好的组件实例会作为回调函数的参数传入 推荐本人对Vue router的详细介绍博客 Vue 路由(Router)详细介绍(切换,传参,通信······)_前端不释卷leo的博客-CSDN博客前言:在一个vue项目中,免不了对组件(页面)进行切换与跳转。而用 Vue.js + Vue Router 创建单页应用,感觉很自然:使用 Vue.js ,我们已经可以通过组合组件来组成应用程序,当你要把 Vue Router 添加进来,我们需要做的是,将组件 (components) 映射到路由 (routes),然后告诉 Vue Router 在哪里渲染它们。话不多说,直接开始!!!准备工作在使用脚手架vue-cli3创建(vue create projectName)一个vue项目时,https://blog.csdn.net/qq_41809113/article/details/121841075?spm=1001.2014.3001.5502 Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式,采用集中式存储管理应用的所有组件的状态。 主要解决如下 两个 问题 1、多个视图依赖同一状态。 2、来自不同视图的行为需要变更同一个状态。 其包含如下模块 State:定义并初始化全局状态。 当然,若应用比较简单,共享状态也比较少,可以用 Vue.observe 去替代 Vuex,省去安装一个库也挺好。 具体细节参考文章 Vuex核心用法,一学就会!(中秋特辑,快来get你的月饼啦)_前端不释卷leo的博客-CSDN博客今天是中秋佳节,祝各位大佬中秋节快乐!来get自己的月饼吧。趁着这个美好的节日,怀着美丽的心情,建造自己的“工厂”,我将以个人理解,用“月饼工厂”,模拟vuex的精简用法,嘻嘻嘻......话不多说,直接“造”(燥)起来。...https://blog.csdn.net/qq_41809113/article/details/120398625?spm=1001.2014.3001.5502 持久化缓存:localStorage、sessionStorage Vuex 中修改 state 唯一渠道是执行 commit 方法,底层通过执行 this._withCommit(fn),且设置_committing标识符为 true,才能修改 state,修改完还需要将标识符置为 false。外部修改是无法设置标识位的,所以通过 watch 监听 state 变化,来判断修改的合法性。 Vue SSR 项目中暂时还没有运用过,后续会写个 Demo 单独成文吧。这边搬运下其他答案。 SSR 服务端渲染,将 HTML 渲染工作放在服务端完成后,将 HTML 返回到浏览器端。 优点:SSR有更好的 SEO,首屏加载更快。 如果是内部系统,SSR其实没有太多必要。如果是对外的项目,维护高可用的node服务器是个难点。 具体细节参考本人博客对Vue3的详细介绍 1、非响应式数据通过 Object.freeze 冻结数据 2、嵌套层级不要过深 3、computed 和 watch 区别使用 4、v-if 和 v-show 区别使用 5、v-for 避免和 v-if 一起使用,且绑定 key 值要唯一 6、列表数据过多采用分页或者虚拟列表 7、组件销毁后清除定时器和事件 8、图片懒加载 9、路由懒加载 10、防抖、节流 11、按需引入外部库 12、keep-alive缓存使用 13、服务端渲染(SSR)和预渲染v-on监听多个方法
常用修饰符
class、style的动态绑定方式
v-show与v-if的区别
beforeCreate
、create
、beforeMount
、mounted
钩子,由 true 变为 false 会触发组件的beforeDestory
、destoryed
方法为什么v-if不能和v-for一起使用
v-for
比v-if
更高的优先级。v-if
比 v-for
更高的优先级。computed与watch的区别与使用场景
slot插槽的理解与使用
slot
在组件模板中提前占据了位置。当复用组件时,使用相关的slot标签时,标签里的内容就会自动替换组件模板中对应slot标签的位置,作为承载分发内容的出口。Vue.$delete和delete的区别
undefined
,其他元素键值不变。Vue.$set如何解决对象新增属性不能响应的问题
Object.defineProperty
的局限性:无法检测对象属性的新增或删除。export function set(target, key, val) {
// 数组
if(Array.isArray(target) && isValidArrayIndex(key)) {
// 修改数组长度,避免索引大于数组长度导致splice错误
target.length = Math.max(target.length, key)
// 利用数组splice触发响应
target.splice(key, 1, val)
return val
}
// key 已经存在,直接修改属性值
if(key in target && !(key in Object.prototype)) {
target[key] = val
return val
}
const ob = target.__ob__
// target 不是响应式数据,直接赋值
if(!ob) {
target[key] = val
return val
}
// 响应式处理属性
defineReactive(ob.value, key, val)
// 派发更新
ob.dep.notify()
return val
}
Vue.$nextTick的原理
import { noop } from 'shared/util'
import { handleError } from './error'
import { isIE, isIOS, isNative } from './env'
// 是否使用微任务标识
export let isUsingMicroTask = false
// 回调函数队列
const callbacks = []
// 异步锁
let pending = false
function flushCallbacks () {
// 表示下一个 flushCallbacks 可以进入浏览器的任务队列了
pending = false
// 防止 nextTick 中包含 nextTick时出现问题,在执行回调函数队列前,提前复制备份,清空回调函数队列
const copies = callbacks.slice(0)
// 清空 callbacks 数组
callbacks.length = 0
for (let i = 0; i < copies.length; i++) {
copies[i]()
}
}
let timerFunc
// 浏览器能力检测
// 使用宏任务或微任务的目的是宏任务和微任务必在同步代码结束之后执行,这时能保证是最终渲染好的DOM。
// 宏任务耗费时间是大于微任务,在浏览器支持的情况下,优先使用微任务。
// 宏任务中效率也有差距,最低的就是 setTimeout
if (typeof Promise !== 'undefined' && isNative(Promise)) {
const p = Promise.resolve()
timerFunc = () => {
p.then(flushCallbacks)
if (isIOS) setTimeout(noop)
}
isUsingMicroTask = true
} else if (!isIE && typeof MutationObserver !== 'undefined' && (
isNative(MutationObserver) ||
MutationObserver.toString() === '[object MutationObserverConstructor]'
)) {
let counter = 1
const observer = new MutationObserver(flushCallbacks)
const textNode = document.createTextNode(String(counter))
observer.observe(textNode, {
characterData: true
})
timerFunc = () => {
counter = (counter + 1) % 2
textNode.data = String(counter)
}
isUsingMicroTask = true
} else if (typeof setImmediate !== 'undefined' && isNative(setImmediate)) {
timerFunc = () => {
setImmediate(flushCallbacks)
}
} else {
timerFunc = () => {
setTimeout(flushCallbacks, 0)
}
}
export function nextTick (cb?: Function, ctx?: Object) {
let _resolve
// 将 nextTick 的回调函数用 try catch 包裹一层,用于异常捕获
// 将包裹后的函数放到 callback 中
callbacks.push(() => {
if (cb) {
try {
cb.call(ctx)
} catch (e) {
handleError(e, ctx, 'nextTick')
}
} else if (_resolve) {
_resolve(ctx)
}
})
// pengding 为 false, 执行 timerFunc
if (!pending) {
// 关上锁
pending = true
timerFunc()
}
if (!cb && typeof Promise !== 'undefined') {
return new Promise(resolve => {
_resolve = resolve
})
}
}
虚拟DOM的理解
function Element(tagName, props, children) {
this.tageName = tagName
this.props = props || {}
this.children = children || []
this.key = props.key
let count = 0
this.children.forEach(child => {
if(child instanceof Element) count += child.count
count++
})
this.count = count
}
const tree = Element('div', { id: container }, [
Element('p', {}, ['real dom'])
Element('ul', {}, [
Element('li', { class: 'item' }, ['item1']),
Element('li', { class: 'item' }, ['item2']),
Element('li', { class: 'item' }, ['item3'])
])
])
Element.prototype.render = function() {
let el = document.createElement(this.tagName)
let props = this.props
for(let key in props) {
el.setAttribute(key, props[key])
}
let children = this.children || []
children.forEach(child => {
let child = (child instanceof Element) ? child.render() : document.createTextNode(child)
el.appendChild(child)
})
return el
}
Diff算法原理
key的作用
动态组件 和 异步组件
is
特性实现。适用于根据数据、动态渲染的场景,即组件类型不确定。Vue.directive 有写过么,有哪些应用场景
Vue 过滤器
Vue.filter('formatName', function(value) {
// 可基于源值做一些处理
return value
})
关于mixin的理解,有什么应用场景
相同
的悬浮定位浮窗,可尝试用 mixin 封装。介绍一下keep-alive
Vue-Router 配置 404 页面
{
path: '*',
name: '404'
component: () => import('./404.vue')
}
Vue-Router 有哪几种导航守卫
const router = new VueRouter({
// 相关配置
})
// 全局路由守卫(跳转每个路由页面前统一处理)
router.beforeEach((to, from, next) => {
...
// 必须执行 next 方法来触发路由跳转
next()
})
next
函数也不会改变导航本身// 全局后置钩子(跳转每个路由页面后统一处理)
router.afterEach((to, from) => {
// ...
})
const router = new VueRouter({
routes: [
{
path: '/home',
component: Home,
// 只有该路由独享
beforeEnter: (to, from, next) => {
}
},
{
path: '/about',
component: About
}
]
})
onst Foo = {
template: `...`,
beforeRouteEnter(to, from, next) {
// 不能获取组件实例 this
// 当守卫执行前,组件实例还没被创建
},
beforeRouteUpdate(to, from, next) {
// 当前路由改变,但是组件被复用时调用
// 可访问实例 this
},
beforeRouteLeave(to, from, next) {
// 导航离开组件时被调用
}
}
Vue-Router 完整的导航解析流程
Vuex 的理解及使用
Getter:依赖 State 中的状态,进行二次包装,不会影响 State 源数据。
Mutation:更改 State 状态的函数,必须是同步。
Action:用于提交 Mutation,可包含任意异步操作。
Module:若应用复杂,Store 会集中一个比较大的对象而显得臃肿,Module允许我们将 Store模块化管理。Vuex 刷新后数据丢失怎么办
Vuex 如何知道 State 是通过 Mutation 修改还是外部修改
Vue SSR 了解么
缺点:服务端负载大。Vue2 与 Vue3 的区别 ?Vue3有哪些优化点?
Vue 性能优化