共计 10065 个字符,预计需要花费 26 分钟才能阅读完成。
从 0 到 1 本人构架一个 vue 我的项目,说说有哪些步骤、哪些重要插件、目录构造你会怎么组织
综合实际类题目,考查实战能力。没有什么相对的正确答案,把平时工作的重点有条理的形容一下即可
思路
- 构建我的项目,创立我的项目根本构造
- 引入必要的插件:
- 代码标准:
prettier
,eslint
- 提交标准:
husky
,lint-staged` - 其余罕用:
svg-loader
,vueuse
,nprogress
- 常见目录构造
答复范例
- 从
0
创立一个我的项目我大抵会做以下事件:我的项目构建、引入必要插件、代码标准、提交标准、罕用库和组件 - 目前
vue3
我的项目我会用vite
或者create-vue
创立我的项目 - 接下来引入必要插件:路由插件
vue-router
、状态治理vuex/pinia
、ui
库我比拟喜爱element-plu
s 和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
:用来放我的项目的页面文件
如何从实在 DOM 到虚构 DOM
波及到 Vue 中的模板编译原理,次要过程:
- 将模板转换成
ast
树,ast
用对象来形容实在的 JS 语法(将实在 DOM 转换成虚构 DOM) - 优化树
- 将
ast
树生成代码
Vue 子组件和父组件执行程序
加载渲染过程:
- 父组件 beforeCreate
- 父组件 created
- 父组件 beforeMount
- 子组件 beforeCreate
- 子组件 created
- 子组件 beforeMount
- 子组件 mounted
- 父组件 mounted
更新过程:
- 父组件 beforeUpdate
- 子组件 beforeUpdate
- 子组件 updated
- 父组件 updated
销毁过程:
- 父组件 beforeDestroy
- 子组件 beforeDestroy
- 子组件 destroyed
- 父组件 destoryed
vue-router 路由钩子函数是什么 执行程序是什么
路由钩子的执行流程, 钩子函数品种有: 全局守卫、路由守卫、组件守卫
残缺的导航解析流程:
- 导航被触发。
- 在失活的组件里调用 beforeRouteLeave 守卫。
- 调用全局的 beforeEach 守卫。
- 在重用的组件里调用 beforeRouteUpdate 守卫 (2.2+)。
- 在路由配置里调用 beforeEnter。
- 解析异步路由组件。
- 在被激活的组件里调用 beforeRouteEnter。
- 调用全局的 beforeResolve 守卫 (2.5+)。
- 导航被确认。
- 调用全局的 afterEach 钩子。
- 触发 DOM 更新。
- 调用 beforeRouteEnter 守卫中传给 next 的回调函数,创立好的组件实例会作为回调函数的参数传入。
用 VNode 来形容一个 DOM 构造
虚构节点就是用一个对象来形容一个实在的 DOM 元素。首先将 template
(实在 DOM)先转成 ast
, ast
树通过 codegen
生成 render
函数, render
函数里的 _c
办法将它转为虚构 dom
理解 nextTick 吗?
异步办法,异步渲染最初一步,与 JS 事件循环分割严密。次要应用了宏工作微工作(setTimeout
、promise
那些),定义了一个异步办法,屡次调用 nextTick
会将办法存入队列,通过异步办法清空以后队列。
参考 前端进阶面试题具体解答
delete 和 Vue.delete 删除数组的区别
delete
只是被删除的元素变成了empty/undefined
其余的元素的键值还是不变。Vue.delete
间接删除了数组 扭转了数组的键值。
对 SPA 单页面的了解,它的优缺点别离是什么?
SPA(single-page application)仅在 Web 页面初始化时加载相应的 HTML、JavaScript 和 CSS。一旦页面加载实现,SPA 不会因为用户的操作而进行页面的从新加载或跳转;取而代之的是利用路由机制实现 HTML 内容的变换,UI 与用户的交互,防止页面的从新加载。
长处:
- 用户体验好、快,内容的扭转不须要从新加载整个页面,防止了不必要的跳转和反复渲染;
- 基于下面一点,SPA 绝对对服务器压力小;
- 前后端职责拆散,架构清晰,前端进行交互逻辑,后端负责数据处理;
毛病:
- 首次加载耗时多:为实现单页 Web 利用性能及显示成果,须要在加载页面的时候将 JavaScript、CSS 对立加载,局部页面按需加载;
- 后退后退路由治理:因为单页利用在一个页面中显示所有的内容,所以不能应用浏览器的后退后退性能,所有的页面切换须要本人建设堆栈治理;
- SEO 难度较大:因为所有的内容都在一个页面中动静替换显示,所以在 SEO 上其有着人造的弱势。
computed 和 watch 有什么区别?
computed:
computed
是计算属性, 也就是计算值, 它更多用于计算值的场景computed
具备缓存性,computed 的值在 getter 执行后是会缓存的,只有在它依赖的属性值扭转之后,下一次获取 computed 的值时才会从新调用对应的 getter 来计算computed
实用于计算比拟耗费性能的计算场景
watch:
- 更多的是「察看」的作用, 相似于某些数据的监听回调, 用于察看
props
$emit
或者本组件的值, 当数据变动时来执行回调进行后续操作 - 无缓存性,页面从新渲染时值不变动也会执行
小结:
- 当咱们要进行数值计算, 而且依赖于其余数据,那么把这个数据设计为 computed
- 如果你须要在某个数据变动时做一些事件,应用 watch 来察看这个数据变动
说说 Vue 的生命周期吧
什么时候被调用?
- beforeCreate:实例初始化之后,数据观测之前调用
- created:实例创立万之后调用。实例实现:数据观测、属性和办法的运算、
watch/event
事件回调。无$el
. - beforeMount:在挂载之前调用,相干
render
函数首次被调用 - mounted:了被新创建的
vm.$el
替换,并挂载到实例下来之后调用改钩子。 - beforeUpdate:数据更新前调用,产生在虚构 DOM 从新渲染和打补丁,在这之后会调用改钩子。
- updated:因为数据更改导致的虚构 DOM 从新渲染和打补丁,在这之后会调用改钩子。
- beforeDestroy:实例销毁前调用,实例依然可用。
- destroyed:实例销毁之后调用,调用后,Vue 实例批示的所有货色都会解绑,所有事件监听器和所有子实例都会被移除
每个生命周期外部能够做什么?
- created:实例曾经创立实现,因为他是最早触发的,所以能够进行一些数据、资源的申请。
- mounted:实例曾经挂载实现,能够进行一些 DOM 操作。
- beforeUpdate:能够在这个钩子中进一步的更改状态,不会触发重渲染。
- updated:能够执行依赖于 DOM 的操作,然而要防止更改状态,可能会导致更新无线循环。
- destroyed:能够执行一些优化操作,清空计时器,解除绑定事件。
ajax 放在哪个生命周期?:个别放在 mounted
中,保障逻辑统一性,因为生命周期是同步执行的, ajax
是异步执行的。复数服务端渲染 ssr
同一放在 created
中,因为服务端渲染不反对 mounted
办法。 什么时候应用 beforeDestroy?:以后页面应用 $on
,须要解绑事件。分明定时器。解除事件绑定, scroll mousemove
。
说一下 Vue 的生命周期
Vue 实例有⼀个残缺的⽣命周期,也就是从开始创立、初始化数据、编译模版、挂载 Dom -> 渲染、更新 -> 渲染、卸载 等⼀系列过程,称这是 Vue 的⽣命周期。
- beforeCreate(创立前):数据观测和初始化事件还未开始,此时 data 的响应式追踪、event/watcher 都还没有被设置,也就是说不能拜访到 data、computed、watch、methods 上的办法和数据。
- created(创立后):实例创立实现,实例上配置的 options 包含 data、computed、watch、methods 等都配置实现,然而此时渲染得节点还未挂载到 DOM,所以不能拜访到
$el
属性。 - beforeMount(挂载前):在挂载开始之前被调用,相干的 render 函数首次被调用。实例已实现以下的配置:编译模板,把 data 外面的数据和模板生成 html。此时还没有挂载 html 到页面上。
- mounted(挂载后):在 el 被新创建的 vm.$el 替换,并挂载到实例下来之后调用。实例已实现以下的配置:用下面编译好的 html 内容替换 el 属性指向的 DOM 对象。实现模板中的 html 渲染到 html 页面中。此过程中进行 ajax 交互。
- beforeUpdate(更新前):响应式数据更新时调用,此时尽管响应式数据更新了,然而对应的实在 DOM 还没有被渲染。
- updated(更新后):在因为数据更改导致的虚构 DOM 从新渲染和打补丁之后调用。此时 DOM 曾经依据响应式数据的变动更新了。调用时,组件 DOM 曾经更新,所以能够执行依赖于 DOM 的操作。然而在大多数状况下,应该防止在此期间更改状态,因为这可能会导致更新有限循环。该钩子在服务器端渲染期间不被调用。
- beforeDestroy(销毁前):实例销毁之前调用。这一步,实例依然齐全可用,
this
仍能获取到实例。 - destroyed(销毁后):实例销毁后调用,调用后,Vue 实例批示的所有货色都会解绑定,所有的事件监听器会被移除,所有的子实例也会被销毁。该钩子在服务端渲染期间不被调用。
另外还有 keep-alive
独有的生命周期,别离为 activated
和 deactivated
。用 keep-alive
包裹的组件在切换时不会进行销毁,而是缓存到内存中并执行 deactivated
钩子函数,命中缓存渲染后会执行 activated
钩子函数。
写过自定义指令吗 原理是什么
指令实质上是装璜器,是 vue 对 HTML 元素的扩大,给 HTML 元素减少自定义性能。vue 编译 DOM 时,会找到指令对象,执行指令的相干办法。
自定义指令有五个生命周期(也叫钩子函数),别离是 bind、inserted、update、componentUpdated、unbind
1. bind:只调用一次,指令第一次绑定到元素时调用。在这里能够进行一次性的初始化设置。2. inserted:被绑定元素插入父节点时调用 (仅保障父节点存在,但不肯定已被插入文档中)。3. update:被绑定于元素所在的模板更新时调用,而无论绑定值是否变动。通过比拟更新前后的绑定值,能够疏忽不必要的模板更新。4. componentUpdated:被绑定元素所在模板实现一次更新周期时调用。5. unbind:只调用一次,指令与元素解绑时调用。
原理
1. 在生成 ast 语法树时,遇到指令会给以后元素增加 directives 属性
2. 通过 genDirectives 生成指令代码
3. 在 patch 前将指令的钩子提取到 cbs 中, 在 patch 过程中调用对应的钩子
4. 当执行指令对应钩子函数时,调用对应指令定义的办法
Vue 的生命周期办法有哪些 个别在哪一步发申请
beforeCreate 在实例初始化之后,数据观测(data observer) 和 event/watcher 事件配置之前被调用。在以后阶段 data、methods、computed 以及 watch 上的数据和办法都不能被拜访
created 实例曾经创立实现之后被调用。在这一步,实例已实现以下的配置:数据观测(data observer),属性和办法的运算,watch/event 事件回调。这里没有 $el, 如果非要想与 Dom 进行交互,能够通过 vm.$nextTick 来拜访 Dom
beforeMount 在挂载开始之前被调用:相干的 render 函数首次被调用。
mounted 在挂载实现后产生,在以后阶段,实在的 Dom 挂载结束,数据实现双向绑定,能够拜访到 Dom 节点
beforeUpdate 数据更新时调用,产生在虚构 DOM 从新渲染和打补丁(patch)之前。能够在这个钩子中进一步地更改状态,这不会触发附加的重渲染过程
updated 产生在更新实现之后,以后阶段组件 Dom 已实现更新。要留神的是防止在此期间更改数据,因为这可能会导致有限循环的更新,该钩子在服务器端渲染期间不被调用。
beforeDestroy 实例销毁之前调用。在这一步,实例依然齐全可用。咱们能够在这时进行善后收尾工作,比方革除计时器。
destroyed Vue 实例销毁后调用。调用后,Vue 实例批示的所有货色都会解绑定,所有的事件监听器会被移除,所有的子实例也会被销毁。该钩子在服务器端渲染期间不被调用。
activated keep-alive 专属,组件被激活时调用
deactivated keep-alive 专属,组件被销毁时调用
异步申请在哪一步发动?
能够在钩子函数 created、beforeMount、mounted 中进行异步申请,因为在这三个钩子函数中,data 曾经创立,能够将服务端端返回的数据进行赋值。
如果异步申请不须要依赖 Dom 举荐在 created 钩子函数中调用异步申请,因为在 created 钩子函数中调用异步申请有以下长处:
- 能更快获取到服务端数据,缩小页面 loading 工夫;
- ssr 不反对 beforeMount、mounted 钩子函数,所以放在 created 中有助于一致性;
Computed 和 Watch 的区别
对于 Computed:
- 它反对缓存,只有依赖的数据产生了变动,才会从新计算
- 不反对异步,当 Computed 中有异步操作时,无奈监听数据的变动
- computed 的值会默认走缓存,计算属性是基于它们的响应式依赖进行缓存的,也就是基于 data 申明过,或者父组件传递过去的 props 中的数据进行计算的。
- 如果一个属性是由其余属性计算而来的,这个属性依赖其余的属性,个别会应用 computed
- 如果 computed 属性的属性值是函数,那么默认应用 get 办法,函数的返回值就是属性的属性值;在 computed 中,属性有一个 get 办法和一个 set 办法,当数据发生变化时,会调用 set 办法。
对于 Watch:
- 它不反对缓存,数据变动时,它就会触发相应的操作
- 反对异步监听
- 监听的函数接管两个参数,第一个参数是最新的值,第二个是变动之前的值
- 当一个属性发生变化时,就须要执行相应的操作
-
监听数据必须是 data 中申明的或者父组件传递过去的 props 中的数据,当发生变化时,会触发其余操作,函数有两个的参数:
- immediate:组件加载立刻触发回调函数
- deep:深度监听,发现数据外部的变动,在简单数据类型中应用,例如数组中的对象发生变化。须要留神的是,deep 无奈监听到数组和对象外部的变动。
当想要执行异步或者低廉的操作以响应一直的变动时,就须要应用 watch。
总结:
- computed 计算属性 : 依赖其它属性值,并且 computed 的值有缓存,只有它依赖的属性值产生扭转,下一次获取 computed 的值时才会从新计算 computed 的值。
- watch 侦听器 : 更多的是 察看 的作用,无缓存性,相似于某些数据的监听回调,每当监听的数据变动时都会执行回调进行后续操作。
使用场景:
- 当须要进行数值计算, 并且依赖于其它数据时,应该应用 computed,因为能够利用 computed 的缓存个性,防止每次获取值时都要从新计算。
- 当须要在数据变动时执行异步或开销较大的操作时,应该应用 watch,应用 watch 选项容许执行异步操作 (拜访一个 API),限度执行该操作的频率,并在失去最终后果前,设置中间状态。这些都是计算属性无奈做到的。
Vue3.0 和 2.0 的响应式原理区别
Vue3.x 改用 Proxy 代替 Object.defineProperty。因为 Proxy 能够间接监听对象和数组的变动,并且有多达 13 种拦挡办法。
相干代码如下
import {mutableHandlers} from "./baseHandlers"; // 代理相干逻辑
import {isObject} from "./util"; // 工具办法
export function reactive(target) {
// 依据不同参数创立不同响应式对象
return createReactiveObject(target, mutableHandlers);
}
function createReactiveObject(target, baseHandler) {if (!isObject(target)) {return target;}
const observed = new Proxy(target, baseHandler);
return observed;
}
const get = createGetter();
const set = createSetter();
function createGetter() {return function get(target, key, receiver) {
// 对获取的值进行喷射
const res = Reflect.get(target, key, receiver);
console.log("属性获取", key);
if (isObject(res)) {
// 如果获取的值是对象类型,则返回以后对象的代理对象
return reactive(res);
}
return res;
};
}
function createSetter() {return function set(target, key, value, receiver) {const oldValue = target[key];
const hadKey = hasOwn(target, key);
const result = Reflect.set(target, key, value, receiver);
if (!hadKey) {console.log("属性新增", key, value);
} else if (hasChanged(value, oldValue)) {console.log("属性值被批改", key, value);
}
return result;
};
}
export const mutableHandlers = {
get, // 当获取属性时调用此办法
set, // 当批改属性时调用此办法
};
形容下 Vue 自定义指令
在 Vue2.0 中,代码复用和形象的次要模式是组件。然而,有的状况下,你依然须要对一般 DOM 元素进行底层操作,这时候就会用到自定义指令。
个别须要对 DOM 元素进行底层操作时应用,尽量只用来操作 DOM 展现,不批改外部的值。当应用自定义指令间接批改 value 值时绑定 v -model 的值也不会同步更新;如必须批改能够在自定义指令中应用 keydown 事件,在 vue 组件中应用 change 事件,回调中批改 vue 数据;
(1)自定义指令根本内容
- 全局定义:
Vue.directive("focus",{})
- 部分定义:
directives:{focus:{}}
-
钩子函数:指令定义对象提供钩子函数
o bind:只调用一次,指令第一次绑定到元素时调用。在这里能够进行一次性的初始化设置。
o inSerted:被绑定元素插入父节点时调用(仅保障父节点存在,但不肯定已被插入文档中)。
o update:所在组件的 VNode 更新时调用,然而可能产生在其子 VNode 更新之前调用。指令的值可能产生了扭转,也可能没有。然而能够通过比拟更新前后的值来疏忽不必要的模板更新。
o ComponentUpdate:指令所在组件的 VNode 及其子 VNode 全副更新后调用。
o unbind:只调用一次,指令与元素解绑时调用。
-
钩子函数参数
o el:绑定元素o bing:指令外围对象,形容指令全副信息属性
o name
o value
o oldValue
o expression
o arg
o modifers
o vnode 虚构节点
o oldVnode:上一个虚构节点(更新钩子函数中才有用)
(2)应用场景
- 一般 DOM 元素进行底层操作的时候,能够应用自定义指令
- 自定义指令是用来操作 DOM 的。只管 Vue 推崇数据驱动视图的理念,但并非所有状况都适宜数据驱动。自定义指令就是一种无效的补充和扩大,不仅可用于定义任何的 DOM 操作,并且是可复用的。
(3)应用案例
高级利用:
- 鼠标聚焦
- 下拉菜单
- 绝对工夫转换
- 滚动动画
高级利用:
- 自定义指令实现图片懒加载
- 自定义指令集成第三方插件
action 与 mutation 的区别
mutation
是同步更新,$watch
严格模式下会报错action
是异步操作,能够获取数据后调用mutation
提交最终数据
为什么 vue 组件中 data 必须是一个函数?
对象为援用类型,当复用组件时,因为数据对象都指向同一个 data 对象,当在一个组件中批改 data 时,其余重用的组件中的 data 会同时被批改;而应用返回对象的函数,因为每次返回的都是一个新对象(Object 的实例),援用地址不同,则不会呈现这个问题。
mixin 和 mixins 区别
mixin
用于全局混入,会影响到每个组件实例,通常插件都是这样做初始化的。
Vue.mixin({beforeCreate() {// ... 逻辑 // 这种形式会影响到每个组件的 beforeCreate 钩子函数},
});
尽管文档不倡议在利用中间接应用 mixin
,然而如果不滥用的话也是很有帮忙的,比方能够全局混入封装好的 ajax
或者一些工具函数等等。
mixins
应该是最常应用的扩大组件的形式了。如果多个组件中有雷同的业务逻辑,就能够将这些逻辑剥离进去,通过 mixins
混入代码,比方上拉下拉加载数据这种逻辑等等。
另外须要留神的是 mixins
混入的钩子函数会先于组件内的钩子函数执行,并且在遇到同名选项的时候也会有选择性的进行合并。
对前端路由的了解
在前端技术晚期,一个 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 去给它生成不同的内容。