前端一面高频 vue 面试题(边面边更)
Vue 组件之间通信方式有哪些
Vue 组件间通信是面试常考的知识点之一,这题有点类似于开放题,你回答出越多方法当然越加分,表明你对 Vue 掌握的越熟练。 Vue 组件间通信只要指以下 3 类通信 :
父子组件通信、隔代组件通信、兄弟组件通信,下面我们分别介绍每种通信方式且会说明此种方法可适用于哪类组件间通信
组件传参的各种方式
组件通信常用方式有以下几种
props / $emit适用 父子组件通信父组件向子组件传递数据是通过
prop传递的,子组件传递数据给父组件是通过$emit触发事件来做到的ref与$parent / $children(vue3废弃)适用 父子组件通信ref:如果在普通的DOM元素上使用,引用指向的就是DOM元素;如果用在子组件上,引用就指向组件实例$parent / $children:访问访问父组件的属性或方法 / 访问子组件的属性或方法EventBus ($emit / $on)适用于 父子、隔代、兄弟组件通信这种方法通过一个空的
Vue实例作为中央事件总线(事件中心),用它来触发事件和监听事件,从而实现任何组件间的通信,包括父子、隔代、兄弟组件$attrs / $listeners(vue3废弃)适用于 隔代组件通信$attrs:包含了父作用域中不被prop所识别 (且获取) 的特性绑定 (class和style除外 )。当一个组件没有声明任何prop时,这里会包含所有父作用域的绑定 (class和style除外 ),并且可以通过v-bind="$attrs"传入内部组件。通常配合inheritAttrs选项一起使用$listeners:包含了父作用域中的 (不含.native修饰器的)v-on事件监听器。它可以通过v-on="$listeners"传入内部组件provide / inject适用于 隔代组件通信祖先组件中通过
provider来提供变量,然后在子孙组件中通过inject来注入变量。provide / injectAPI 主要解决了跨级组件间的通信问题, 不过它的使用场景,主要是子组件获取上级组件的状态 ,跨级组件间建立了一种主动提供与依赖注入的关系$root适用于 隔代组件通信 访问根组件中的属性或方法,是根组件,不是父组件。$root只对根组件有用Vuex适用于 父子、隔代、兄弟组件通信Vuex是一个专为Vue.js应用程序开发的状态管理模式。每一个Vuex应用的核心就是store(仓库)。“store” 基本上就是一个容器,它包含着你的应用中大部分的状态 (state)Vuex的状态存储是响应式的。当Vue组件从store中读取状态的时候,若store中的状态发生变化,那么相应的组件也会相应地得到高效更新。改变
store中的状态的唯一途径就是显式地提交 (commit)mutation。这样使得我们可以方便地跟踪每一个状态的变化。
根据组件之间关系讨论组件通信最为清晰有效
父子组件:
props/$emit/$parent/ref兄弟组件:
$parent/eventbus/vuex跨层级关系:
eventbus/vuex/provide+inject/$attrs + $listeners/$root
下面演示组件之间通讯三种情况: 父传子、子传父、兄弟组件之间的通讯
1. 父子组件通信
使用
props,父组件可以使用props向子组件传递数据。
父组件vue模板father.vue:
子组件vue模板child.vue:
回调函数(callBack)
父传子:将父组件里定义的method作为props传入子组件
子组件向父组件通信
父组件向子组件传递事件方法,子组件通过
$emit触发事件,回调给父组件
父组件vue模板father.vue:
子组件vue模板child.vue:
2. provide / inject 跨级访问祖先组件的数据
父组件通过使用provide(){return{}}提供需要传递的数据
子组件通过使用inject:[“参数1”,”参数2”,…]接收父组件传递的参数
3. children 获取父组件实例和子组件实例的集合
this.$parent可以直接访问该组件的父实例或组件父组件也可以通过
this.$children访问它所有的子组件;需要注意$children并不保证顺序,也不是响应式的
4. listeners 多级组件通信
$attrs包含了从父组件传过来的所有props属性
$listeners包含了父组件监听的所有事件
5. ref 父子组件通信
6. 非父子, 兄弟组件之间通信
vue2中废弃了broadcast广播和分发事件的方法。父子组件中可以用props和$emit()。如何实现非父子组件间的通信,可以通过实例一个vue实例Bus作为媒介,要相互通信的兄弟组件之中,都引入Bus,然后通过分别调用 Bus 事件触发和监听来实现通信和参数传递。Bus.js可以是这样:
另一个组件也在钩子函数中监听on事件
7. $root 访问根组件中的属性或方法
作用:访问根组件中的属性或方法
注意:是根组件,不是父组件。
$root只对根组件有用
8. vuex
适用场景: 复杂关系的组件数据传递
Vuex 作用相当于一个用来存储共享变量的容器
state用来存放共享变量的地方getter,可以增加一个getter派生状态,(相当于store中的计算属性),用来获得共享变量的值mutations用来存放修改state的方法。actions也是用来存放修改 state 的方法,不过action是在mutations的基础上进行。常用来做一些异步操作
小结
父子关系的组件数据传递选择
props与$emit进行传递,也可选择ref兄弟关系的组件数据传递可选择
$bus,其次可以选择$parent进行传递祖先与后代组件数据传递可选择
attrs与listeners或者Provide与Inject复杂关系的组件数据传递可以通过
vuex存放共享的变量
Vue 组件 data 为什么必须是个函数?
根实例对象
data可以是对象也可以是函数 (根实例是单例),不会产生数据污染情况组件实例对象
data必须为函数 一个组件被复用多次的话,也就会创建多个实例。本质上,这些实例用的都是同一个构造函数。如果data是对象的话,对象属于引用类型,会影响到所有的实例。所以为了保证组件不同的实例之间data不冲突,data必须是一个函数,
简版理解
相关源码
Vue 修饰符有哪些
事件修饰符
.stop 阻止事件继续传播
.prevent 阻止标签默认行为
.capture 使用事件捕获模式,即元素自身触发的事件先在此处处理,然后才交由内部元素进行处理
.self 只当在 event.target 是当前元素自身时触发处理函数
.once 事件将只会触发一次
.passive 告诉浏览器你不想阻止事件的默认行为
v-model 的修饰符
.lazy 通过这个修饰符,转变为在 change 事件再同步
.number 自动将用户的输入值转化为数值类型
.trim 自动过滤用户输入的首尾空格
键盘事件的修饰符
.enter
.tab
.delete (捕获“删除”和“退格”键)
.esc
.space
.up
.down
.left
.right
系统修饰键
.ctrl
.alt
.shift
.meta
鼠标按钮修饰符
.left
.right
.middle
理解 Vue 运行机制全局概览
全局概览
首先我们来看一下笔者画的内部流程图。
大家第一次看到这个图一定是一头雾水的,没有关系,我们来逐个讲一下这些模块的作用以及调用关系。相信讲完之后大家对Vue.js内部运行机制会有一个大概的认识。
初始化及挂载
在
new Vue()之后。 Vue 会调用_init函数进行初始化,也就是这里的init过程,它会初始化生命周期、事件、 props、 methods、 data、 computed 与 watch 等。其中最重要的是通过Object.defineProperty设置setter与getter函数,用来实现「 响应式 」以及「 依赖收集 」,后面会详细讲到,这里只要有一个印象即可。
初始化之后调用
$mount会挂载组件,如果是运行时编译,即不存在 render function 但是存在 template 的情况,需要进行「 编译 」步骤。
编译
compile 编译可以分成 parse、optimize 与 generate 三个阶段,最终需要得到 render function。
1. parse
parse 会用正则等方式解析 template 模板中的指令、class、style 等数据,形成 AST。
2. optimize
optimize的主要作用是标记 static 静态节点,这是 Vue 在编译过程中的一处优化,后面当update更新界面时,会有一个patch的过程, diff 算法会直接跳过静态节点,从而减少了比较的过程,优化了patch的性能。
3. generate
generate是将 AST 转化成render function字符串的过程,得到结果是render的字符串以及 staticRenderFns 字符串。
在经历过
parse、optimize与generate这三个阶段以后,组件中就会存在渲染VNode所需的render function了。
响应式
接下来也就是 Vue.js 响应式核心部分。
这里的
getter跟setter已经在之前介绍过了,在init的时候通过Object.defineProperty进行了绑定,它使得当被设置的对象被读取的时候会执行getter函数,而在当被赋值的时候会执行setter函数。
当
render function被渲染的时候,因为会读取所需对象的值,所以会触发getter函数进行「 依赖收集 」,「 依赖收集 」的目的是将观察者Watcher对象存放到当前闭包中的订阅者Dep的subs中。形成如下所示的这样一个关系。
在修改对象的值的时候,会触发对应的
setter,setter通知之前「 依赖收集 」得到的 Dep 中的每一个 Watcher,告诉它们自己的值改变了,需要重新渲染视图。这时候这些 Watcher 就会开始调用update来更新视图,当然这中间还有一个patch的过程以及使用队列来异步更新的策略,这个我们后面再讲。
Virtual DOM
我们知道,
render function会被转化成VNode节点。Virtual DOM其实就是一棵以 JavaScript 对象( VNode 节点)作为基础的树,用对象属性来描述节点,实际上它只是一层对真实 DOM 的抽象。最终可以通过一系列操作使这棵树映射到真实环境上。由于 Virtual DOM 是以 JavaScript 对象为基础而不依赖真实平台环境,所以使它具有了跨平台的能力,比如说浏览器平台、Weex、Node 等。
比如说下面这样一个例子:
渲染后可以得到
这只是一个简单的例子,实际上的节点有更多的属性来标志节点,比如 isStatic (代表是否为静态节点)、 isComment (代表是否为注释节点)等。
更新视图
前面我们说到,在修改一个对象值的时候,会通过
setter -> Watcher -> update的流程来修改对应的视图,那么最终是如何更新视图的呢?当数据变化后,执行 render function 就可以得到一个新的 VNode 节点,我们如果想要得到新的视图,最简单粗暴的方法就是直接解析这个新的
VNode节点,然后用innerHTML直接全部渲染到真实DOM中。但是其实我们只对其中的一小块内容进行了修改,这样做似乎有些「 浪费 」。那么我们为什么不能只修改那些「改变了的地方」呢?这个时候就要介绍我们的「
patch」了。我们会将新的VNode与旧的VNode一起传入patch进行比较,经过 diff 算法得出它们的「 差异 」。最后我们只需要将这些「 差异 」的对应 DOM 进行修改即可。
再看全局
回过头再来看看这张图,是不是大脑中已经有一个大概的脉络了呢?
Vue 路由 hash 模式和 history 模式
1. hash模式
早期的前端路由的实现就是基于 location.hash 来实现的。其实现原理很简单,location.hash 的值就是 URL中 # 后面的内容。比如下面这个网站,它的 location.hash 的值为 '#search'
hash 路由模式的实现主要是基于下面几个特性
URL中hash值只是客户端的一种状态,也就是说当向服务器端发出请求时,hash部分不会被发送;hash值的改变,都会在浏览器的访问历史中增加一个记录。因此我们能通过浏览器的回退、前进按钮控制hash的切换;可以通过
a标签,并设置href属性,当用户点击这个标签后,URL的hash值会发生改变;或者使用JavaScript来对loaction.hash进行赋值,改变URL的hash值;我们可以使用
hashchange事件来监听hash值的变化,从而对页面进行跳转(渲染)
每一次改变 hash(window.location.hash),都会在浏览器的访问历史中增加一个记录利用 hash 的以上特点,就可以来实现前端路由“更新视图但不重新请求页面”的功能了
特点 :兼容性好但是不美观
2. history模式
history采用HTML5的新特性;且提供了两个新方法: pushState(), replaceState()可以对浏览器历史记录栈进行修改,以及popState事件的监听到状态变更
这两个方法有个共同的特点:当调用他们修改浏览器历史记录栈后,虽然当前 URL 改变了,但浏览器不会刷新页面,这就为单页应用前端路由“更新视图但不重新请求页面”提供了基础。
history 路由模式的实现主要基于存在下面几个特性:
pushState和repalceState两个API来操作实现URL的变化 ;我们可以使用
popstate事件来监听url的变化,从而对页面进行跳转(渲染);history.pushState()或history.replaceState()不会触发popstate事件,这时我们需要手动触发页面跳转(渲染)。
特点 :虽然美观,但是刷新会出现 404 需要后端进行配置
ref 和 reactive 异同
这是Vue3数据响应式中非常重要的两个概念,跟我们写代码关系也很大
ref接收内部值(inner value)返回响应式Ref对象,reactive返回响应式代理对象从定义上看
ref通常用于处理单值的响应式,reactive用于处理对象类型的数据响应式两者均是用于构造响应式数据,但是
ref主要解决原始值的响应式问题ref返回的响应式数据在 JS 中使用需要加上.value才能访问其值,在视图中使用会自动脱ref,不需要.value;ref可以接收对象或数组等非原始值,但内部依然是reactive实现响应式;reactive内部如果接收Ref 对象会自动脱ref;使用展开运算符(...)展开reactive返回的响应式对象会使其失去响应性,可以结合toRefs()将值转换为Ref对象之后再展开。reactive内部使用Proxy代理传入对象并拦截该对象各种操作,从而实现响应式。ref内部封装一个RefImpl类,并设置get value/set value,拦截用户对值的访问,从而实现响应式
参考 前端进阶面试题详细解答
Vue.observable 你有了解过吗?说说看
一、Observable 是什么
Observable 翻译过来我们可以理解成可观察的
我们先来看一下其在Vue中的定义
Vue.observable,让一个对象变成响应式数据。Vue内部会用它来处理data函数返回的对象
返回的对象可以直接用于渲染函数和计算属性内,并且会在发生变更时触发相应的更新。也可以作为最小化的跨组件状态存储器
其作用等同于
在 Vue 2.x 中,被传入的对象会直接被 Vue.observable 变更,它和被返回的对象是同一个对象
在 Vue 3.x 中,则会返回一个可响应的代理,而对源对象直接进行变更仍然是不可响应的
二、使用场景
在非父子组件通信时,可以使用通常的bus或者使用vuex,但是实现的功能不是太复杂,而使用上面两个又有点繁琐。这时,observable就是一个很好的选择
创建一个js文件
在.vue文件中直接使用即可
三、原理分析
源码位置:src\core\observer\index.js
Observer类
walk函数
defineReactive方法
Vue 中如何检测数组变化
前言
Vue 不能检测到以下数组的变动:
当你利用索引直接设置一个数组项时,例如:
vm.items[indexOfItem] = newValue当你修改数组的长度时,例如:
vm.items.length = newLength
Vue 提供了以下操作方法
分析
数组考虑性能原因没有用
defineProperty对数组的每一项进行拦截,而是选择对7种数组(push,shift,pop,splice,unshift,sort,reverse)方法进行重写(AOP切片思想)
所以在 Vue 中修改数组的索引和长度是无法监控到的。需要通过以上 7 种变异方法修改数组才会触发数组对应的 watcher 进行更新
用函数劫持的方式,重写了数组方法,具体呢就是更改了数组的原型,更改成自己的,用户调数组的一些方法的时候,走的就是自己的方法,然后通知视图去更新
数组里每一项可能是对象,那么我就是会对数组的每一项进行观测,(且只有数组里的对象才能进行观测,观测过的也不会进行观测)
原理
Vue将data中的数组,进行了原型链重写。指向了自己定义的数组原型方法,这样当调用数组api时,可以通知依赖更新,如果数组中包含着引用类型。会对数组中的引用类型再次进行监控。
手写简版分析
源码分析
vue3:改用proxy,可直接监听对象数组的变化
Vue template 到 render 的过程
vue 的模版编译过程主要如下:template -> ast -> render 函数
vue 在模版编译版本的码中会执行 compileToFunctions 将 template 转化为 render 函数:
CompileToFunctions 中的主要逻辑如下∶ (1)调用 parse 方法将 template 转化为 ast(抽象语法树)
parse 的目标:把 tamplate 转换为 AST 树,它是一种用 JavaScript 对象的形式来描述整个模板。
解析过程:利用正则表达式顺序解析模板,当解析到开始标签、闭合标签、文本的时候都会分别执行对应的 回调函数,来达到构造 AST 树的目的。
AST 元素节点总共三种类型:type 为 1 表示普通元素、2 为表达式、3 为纯文本
(2)对静态节点做优化
这个过程主要分析出哪些是静态节点,给其打一个标记,为后续更新渲染可以直接跳过静态节点做优化
深度遍历 AST,查看每个子树的节点元素是否为静态节点或者静态节点根。如果为静态节点,他们生成的 DOM 永远不会改变,这对运行时模板更新起到了极大的优化作用。
(3)生成代码
generate 将 ast 抽象语法树编译成 render 字符串并将静态部分放到 staticRenderFns 中,最后通过 new Function(`` render``) 生成 render 函数。
Vue 模版编译原理
vue 中的模板 template 无法被浏览器解析并渲染,因为这不属于浏览器的标准,不是正确的 HTML 语法,所有需要将 template 转化成一个 JavaScript 函数,这样浏览器就可以执行这一个函数并渲染出对应的 HTML 元素,就可以让视图跑起来了,这一个转化的过程,就成为模板编译。模板编译又分三个阶段,解析 parse,优化 optimize,生成 generate,最终生成可执行函数 render。
解析阶段:使用大量的正则表达式对 template 字符串进行解析,将标签、指令、属性等转化为抽象语法树 AST。
优化阶段:遍历 AST,找到其中的一些静态节点并进行标记,方便在页面重渲染的时候进行 diff 比较时,直接跳过这一些静态节点,优化 runtime 的性能。
生成阶段:将最终的 AST 转化为 render 函数字符串。
对前端路由的理解
在前端技术早期,一个 url 对应一个页面,如果要从 A 页面切换到 B 页面,那么必然伴随着页面的刷新。这个体验并不好,不过在最初也是无奈之举——用户只有在刷新页面的情况下,才可以重新去请求数据。
后来,改变发生了——Ajax 出现了,它允许人们在不刷新页面的情况下发起请求;与之共生的,还有“不刷新页面即可更新页面内容”这种需求。在这样的背景下,出现了 SPA(单页面应用)。
SPA 极大地提升了用户体验,它允许页面在不刷新的情况下更新页面内容,使内容的切换更加流畅。但是在 SPA 诞生之初,人们并没有考虑到“定位”这个问题——在内容切换前后,页面的 URL 都是一样的,这就带来了两个问题:
SPA 其实并不知道当前的页面“进展到了哪一步”。可能在一个站点下经过了反复的“前进”才终于唤出了某一块内容,但是此时只要刷新一下页面,一切就会被清零,必须重复之前的操作、才可以重新对内容进行定位——SPA 并不会“记住”你的操作。
由于有且仅有一个 URL 给页面做映射,这对 SEO 也不够友好,搜索引擎无法收集全面的信息
为了解决这个问题,前端路由出现了。
前端路由可以帮助我们在仅有一个页面的情况下,“记住”用户当前走到了哪一步——为 SPA 中的各个视图匹配一个唯一标识。这意味着用户前进、后退触发的新内容,都会映射到不同的 URL 上去。此时即便他刷新页面,因为当前的 URL 可以标识出他所处的位置,因此内容也不会丢失。
那么如何实现这个目的呢?首先要解决两个问题:
当用户刷新页面时,浏览器会默认根据当前 URL 对资源进行重新定位(发送请求)。这个动作对 SPA 是不必要的,因为我们的 SPA 作为单页面,无论如何也只会有一个资源与之对应。此时若走正常的请求-刷新流程,反而会使用户的前进后退操作无法被记录。
单页面应用对服务端来说,就是一个 URL、一套资源,那么如何做到用“不同的 URL”来映射不同的视图内容呢?
从这两个问题来看,服务端已经完全救不了这个场景了。所以要靠咱们前端自力更生,不然怎么叫“前端路由”呢?作为前端,可以提供这样的解决思路:
拦截用户的刷新操作,避免服务端盲目响应、返回不符合预期的资源内容。把刷新这个动作完全放到前端逻辑里消化掉。
感知 URL 的变化。这里不是说要改造 URL、凭空制造出 N 个 URL 来。而是说 URL 还是那个 URL,只不过我们可以给它做一些微小的处理——这些处理并不会影响 URL 本身的性质,不会影响服务器对它的识别,只有我们前端感知的到。一旦我们感知到了,我们就根据这些变化、用 JS 去给它生成不同的内容。
如何理解 Vue 中模板编译原理
Vue的编译过程就是将template转化为render函数的过程
解析生成 AST 树 将
template模板转化成AST语法树,使用大量的正则表达式对模板进行解析,遇到标签、文本的时候都会执行对应的钩子进行相关处理标记优化 对静态语法做静态标记
markup(静态节点如div下有p标签内容不会变化)diff来做优化 静态节点跳过diff操作Vue的数据是响应式的,但其实模板中并不是所有的数据都是响应式的。有一些数据首次渲染后就不会再变化,对应的DOM也不会变化。那么优化过程就是深度遍历AST树,按照相关条件对树节点进行标记。这些被标记的节点(静态节点)我们就可以跳过对它们的比对,对运行时的模板起到很大的优化作用等待后续节点更新,如果是静态的,不会在比较
children了代码生成 编译的最后一步是将优化后的
AST树转换为可执行的代码
回答范例
思路
引入
vue编译器概念说明编译器的必要性
阐述编译器工作流程
回答范例
Vue中有个独特的编译器模块,称为compiler,它的主要作用是将用户编写的template编译为js中可执行的render函数。之所以需要这个编译过程是为了便于前端能高效的编写视图模板。相比而言,我们还是更愿意用
HTML来编写视图,直观且高效。手写render函数不仅效率底下,而且失去了编译期的优化能力。在
Vue中编译器会先对template进行解析,这一步称为parse,结束之后会得到一个JS对象,我们称为 抽象语法树 AST ,然后是对AST进行深加工的转换过程,这一步成为transform,最后将前面得到的AST生成为JS代码,也就是render函数
可能的追问
Vue中编译器何时执行?
在
new Vue()之后。Vue会调用_init函数进行初始化,也就是这里的 init过程,它会初始化生命周期、事件、props、methods、data、computed与watch等。其中最重要的是通过Object.defineProperty设置setter与getter函数,用来实现「响应式」以及「依赖收集」
初始化之后调用
$mount会挂载组件,如果是运行时编译,即不存在render function但是存在template的情况,需要进行「编译」步骤compile编译可以分成parse、optimize与generate三个阶段,最终需要得到render function
React有没有编译器?
react 使用babel将JSX语法解析
源码分析
Vue 的生命周期方法有哪些
Vue实例有一个完整的生命周期,也就是从开始创建、初始化数据、编译模版、挂载Dom -> 渲染、更新 -> 渲染、卸载等一系列过程,我们称这是Vue的生命周期Vue生命周期总共分为 8 个阶段创建前/后,载入前/后,更新前/后,销毁前/后
beforeCreate=>created=>beforeMount=>Mounted=>beforeUpdate=>updated=>beforeDestroy=>destroyed。keep-alive下:activateddeactivated
其他几个生命周期
要掌握每个生命周期内部可以做什么事
beforeCreate初始化vue实例,进行数据观测。执行时组件实例还未创建,通常用于插件开发中执行一些初始化任务created组件初始化完毕,可以访问各种数据,获取接口数据等beforeMount此阶段vm.el虽已完成DOM初始化,但并未挂载在el选项上mounted实例已经挂载完成,可以进行一些DOM操作beforeUpdate更新前,可用于获取更新前各种状态。此时view层还未更新,可用于获取更新前各种状态。可以在这个钩子中进一步地更改状态,这不会触发附加的重渲染过程。updated完成view层的更新,更新后,所有状态已是最新。可以执行依赖于DOM的操作。然而在大多数情况下,你应该避免在此期间更改状态,因为这可能会导致更新无限循环。 该钩子在服务器端渲染期间不被调用。destroyed可以执行一些优化操作,清空定时器,解除绑定事件vue3
beforeunmount:实例被销毁前调用,可用于一些定时器或订阅的取消vue3
unmounted:销毁一个实例。可清理它与其它实例的连接,解绑它的全部指令及事件监听器
组合式 API 生命周期钩子
你可以通过在生命周期钩子前面加上 “on” 来访问组件的生命周期钩子。
下表包含如何在 setup() 内部调用生命周期钩子:
因为
setup是围绕beforeCreate和created生命周期钩子运行的,所以不需要显式地定义它们。换句话说,在这些钩子中编写的任何代码都应该直接在setup函数中编写
setup和created谁先执行?
beforeCreate:组件被创建出来,组件的methods和data还没初始化好setup:在beforeCreate和created之间执行created:组件被创建出来,组件的methods和data已经初始化好了
由于在执行
setup的时候,created还没有创建好,所以在setup函数内我们是无法使用data和methods的。所以vue为了让我们避免错误的使用,直接将setup函数内的this执行指向undefined
其他问题
什么是 vue 生命周期? Vue 实例从创建到销毁的过程,就是生命周期。从开始创建、初始化数据、编译模板、挂载 Dom→渲染、更新→渲染、销毁等一系列过程,称之为
Vue的生命周期。vue 生命周期的作用是什么? 它的生命周期中有多个事件钩子,让我们在控制整个 Vue 实例的过程时更容易形成好的逻辑。
vue 生命周期总共有几个阶段? 它可以总共分为
8个阶段:创建前/后、载入前/后、更新前/后、销毁前/销毁后。第一次页面加载会触发哪几个钩子? 会触发下面这几个
beforeCreate、created、beforeMount、mounted。你的接口请求一般放在哪个生命周期中? 接口请求一般放在
mounted中,但需要注意的是服务端渲染时不支持mounted,需要放到created中DOM 渲染在哪个周期中就已经完成? 在
mounted中,注意
mounted不会承诺所有的子组件也都一起被挂载。如果你希望等到整个视图都渲染完毕,可以用vm.$nextTick替换掉mounted
这样做的好处在于,无论你的模块文件夹内部有多乱,外部引用的时候,都是从一个入口文件引入,这样就很好的实现了隔离,如果后续有重构需求,你就会发现这种方式的优点
就近原则,紧耦合的文件应该放到一起,且应以相对路径引用
使用相对路径可以保证模块内部的独立性
举个例子
假设我们现在的 seller 目录是在 src/pages/seller,如果我们后续发生了路由变更,需要加一个层级,变成 src/pages/user/seller。
如果我们采用第一种相对路径的方式,那就可以直接将整个文件夹拖过去就好,seller 文件夹内部不需要做任何变更。
但是如果我们采用第二种绝对路径的方式,移动文件夹的同时,还需要对每个 import 的路径做修改
公共的文件应该以绝对路径的方式从根目录引用
公共指的是多个路由模块共用,如一些公共的组件,我们可以放在src/components下
在使用到的页面中,采用绝对路径的形式引用
同样的,如果我们需要对文件夹结构进行调整。将 /src/components/input 变成 /src/components/new/input,如果使用绝对路径,只需要全局搜索替换
再加上绝对路径有全局的语义,相对路径有独立模块的语义
src 外的文件不应该被引入
vue-cli脚手架已经帮我们做了相关的约束了,正常我们的前端项目都会有个src文件夹,里面放着所有的项目需要的资源,js,css, png, svg 等等。src 外会放一些项目配置,依赖,环境等文件
这样的好处是方便划分项目代码文件和配置文件
二、目录结构
单页面目录结构
多页面目录结构
小结
项目的目录结构很重要,因为目录结构能体现很多东西,怎么规划目录结构可能每个人有自己的理解,但是按照一定的规范去进行目录的设计,能让项目整个架构看起来更为简洁,更加易用
delete 和 Vue.delete 删除数组的区别
delete只是被删除的元素变成了empty/undefined其他的元素的键值还是不变。Vue.delete直接删除了数组 改变了数组的键值。
从 0 到 1 自己构架一个 vue 项目,说说有哪些步骤、哪些重要插件、目录结构你会怎么组织
综合实践类题目,考查实战能力。没有什么绝对的正确答案,把平时工作的重点有条理的描述一下即可
思路
构建项目,创建项目基本结构
引入必要的插件:
代码规范:
prettier,eslint提交规范:
husky,lint-staged`其他常用:
svg-loader,vueuse,nprogress常见目录结构
回答范例
从
0创建一个项目我大致会做以下事情:项目构建、引入必要插件、代码规范、提交规范、常用库和组件目前
vue3项目我会用vite或者create-vue创建项目接下来引入必要插件:路由插件
vue-router、状态管理vuex/pinia、ui库我比较喜欢element-plus 和antd-vue、http工具我会选axios其他比较常用的库有
vueuse,nprogress,图标可以使用vite-svg-loader下面是代码规范:结合
prettier和eslint即可最后是提交规范,可以使用
husky,lint-staged,commitlint目录结构我有如下习惯:
.vscode:用来放项目中的vscode配置
plugins:用来放vite插件的plugin配置public:用来放一些诸如 页头icon之类的公共文件,会被打包到dist根目录下src:用来放项目代码文件api:用来放http的一些接口配置assets:用来放一些CSS之类的静态资源components:用来放项目通用组件layout:用来放项目的布局router:用来放项目的路由配置store:用来放状态管理Pinia的配置utils:用来放项目中的工具方法类views:用来放项目的页面文件
v-on 可以监听多个方法吗?
可以监听多个方法
v-on 常用修饰符
.stop该修饰符将阻止事件向上冒泡。同理于调用event.stopPropagation()方法.prevent该修饰符会阻止当前事件的默认行为。同理于调用event.preventDefault()方法.self该指令只当事件是从事件绑定的元素本身触发时才触发回调.once该修饰符表示绑定的事件只会被触发一次
v-once 的使用场景有哪些
分析
v-once是Vue中内置指令,很有用的API,在优化方面经常会用到
体验
仅渲染元素和组件一次,并且跳过未来更新
回答范例
v-once是vue的内置指令,作用是仅渲染指定组件或元素一次,并跳过未来对其更新如果我们有一些元素或者组件在初始化渲染之后不再需要变化,这种情况下适合使用
v-once,这样哪怕这些数据变化,vue也会跳过更新,是一种代码优化手段我们只需要作用的组件或元素上加上
v-once即可vue3.2之后,又增加了v-memo指令,可以有条件缓存部分模板并控制它们的更新,可以说控制力更强了编译器发现元素上面有
v-once时,会将首次计算结果存入缓存对象,组件再次渲染时就会从缓存获取,从而避免再次计算
原理
下面例子使用了v-once:
我们发现v-once出现后,编译器会缓存作用元素或组件,从而避免以后更新时重新计算这一部分:
Class 与 Style 如何动态绑定
Class 可以通过对象语法和数组语法进行动态绑定
对象语法:
数组语法:
Style 也可以通过对象语法和数组语法进行动态绑定
对象语法:
数组语法:
vue 中使用了哪些设计模式
工厂模式 传入参数即可创建实例:虚拟
DOM根据参数的不同返回基础标签的Vnode和组件Vnode单例模式 整个程序有且仅有一个实例:
vuex和vue-router的插件注册方法install判断如果系统存在实例就直接返回掉发布-订阅模式 (vue 事件机制)
观察者模式 (响应式数据原理)
装饰模式: (@装饰器的用法)
策略模式 策略模式指对象有某个行为,但是在不同的场景中,该行为有不同的实现方案-比如选项的合并策略










评论