前端 vue 面试题
Vue3 有了解过吗?能说说跟 vue2 的区别吗?
1. 哪些变化
从上图中,我们可以概览Vue3
的新特性,如下:
速度更快
体积减少
更易维护
更接近原生
更易使用
1.1 速度更快
vue3
相比vue2
重写了虚拟
Dom
实现编译模板的优化
更高效的组件初始化
undate
性能提高 1.3~2 倍SSR
速度提高了 2~3 倍
1.2 体积更小
通过webpack
的tree-shaking
功能,可以将无用模块“剪辑”,仅打包需要的
能够tree-shaking
,有两大好处:
对开发人员,能够对
vue
实现更多其他的功能,而不必担忧整体体积过大对使用者,打包出来的包体积变小了
vue
可以开发出更多其他的功能,而不必担忧vue
打包出来的整体体积过多
1.3 更易维护
compositon Api
可与现有的
Options API
一起使用灵活的逻辑组合与复用
Vue3
模块可以和其他框架搭配使用
更好的 Typescript 支持
VUE3
是基于typescipt
编写的,可以享受到自动的类型定义提示
1.4 编译器重写
1.5 更接近原生
可以自定义渲染 API
1.6 更易使用
响应式 Api
暴露出来
轻松识别组件重新渲染原因
2. Vue3 新增特性
Vue 3 中需要关注的一些新功能包括:
framents
Teleport
composition Api
createRenderer
2.1 framents
在 Vue3.x
中,组件现在支持有多个根节点
2.2 Teleport
Teleport
是一种能够将我们的模板移动到 DOM
中 Vue app
之外的其他位置的技术,就有点像哆啦 A 梦的“任意门”
在vue2
中,像 modals
,toast
等这样的元素,如果我们嵌套在 Vue
的某个组件内部,那么处理嵌套组件的定位、z-index
和样式就会变得很困难
通过Teleport
,我们可以在组件的逻辑位置写模板代码,然后在 Vue
应用范围之外渲染它
2.3 createRenderer
通过createRenderer
,我们能够构建自定义渲染器,我们能够将 vue
的开发模型扩展到其他平台
我们可以将其生成在canvas
画布上
关于createRenderer
,我们了解下基本使用,就不展开讲述了
2.4 composition Api
composition Api,也就是组合式api
,通过这种形式,我们能够更加容易维护我们的代码,将相同功能的变量进行一个集中式的管理
关于compositon api
的使用,这里以下图展开
简单使用:
3. 非兼容变更
3.1 Global API
全局
Vue API
已更改为使用应用程序实例全局和内部
API
已经被重构为可tree-shakable
3.2 模板指令
组件上
v-model
用法已更改<template v-for>
和 非v-for
节点上key
用法已更改在同一元素上使用的
v-if
和v-for
优先级已更改v-bind="object"
现在排序敏感v-for
中的ref
不再注册ref
数组
3.3 组件
只能使用普通函数创建功能组件
functional
属性在单文件组件(SFC)
异步组件现在需要
defineAsyncComponent
方法来创建
3.4 渲染函数
渲染函数
API
改变$scopedSlots
property 已删除,所有插槽都通过$slots
作为函数暴露自定义指令 API 已更改为与组件生命周期一致
一些转换
class
被重命名了:v-enter
->v-enter-from
v-leave
->v-leave-from
组件
watch
选项和实例方法$watch
不再支持点分隔字符串路径,请改用计算函数作为参数在
Vue 2.x
中,应用根容器的outerHTML
将替换为根组件模板 (如果根组件没有模板/渲染选项,则最终编译为模板)。VUE3.x
现在使用应用程序容器的innerHTML
。
3.5 其他小改变
destroyed
生命周期选项被重命名为unmounted
beforeDestroy
生命周期选项被重命名为beforeUnmount
[prop default
工厂函数不再有权访问this
是上下文自定义指令 API 已更改为与组件生命周期一致
data
应始终声明为函数来自
mixin
的data
选项现在可简单地合并attribute
强制策略已更改一些过渡
class
被重命名组建 watch 选项和实例方法
$watch
不再支持以点分隔的字符串路径。请改用计算属性函数作为参数。<template>
没有特殊指令的标记 (v-if/else-if/else
、v-for
或v-slot
) 现在被视为普通元素,并将生成原生的<template>
元素,而不是渲染其内部内容。在
Vue 2.x
中,应用根容器的outerHTML
将替换为根组件模板 (如果根组件没有模板/渲染选项,则最终编译为模板)。Vue 3.x
现在使用应用容器的innerHTML
,这意味着容器本身不再被视为模板的一部分。
3.6 移除 API
keyCode
支持作为v-on
的修饰符$on
,$off
和$once
实例方法过滤
filter
内联模板
attribute
$destroy
实例方法。用户不应再手动管理单个Vue
组件的生命周期。
Vue data 中某一个属性的值发生改变后,视图会立即同步执行重新渲染吗?
不会立即同步执行重新渲染。Vue 实现响应式并不是数据发生变化之后 DOM 立即变化,而是按一定的策略进行 DOM 的更新。Vue 在更新 DOM 时是异步执行的。只要侦听到数据变化, Vue 将开启一个队列,并缓冲在同一事件循环中发生的所有数据变更。
如果同一个 watcher 被多次触发,只会被推入到队列中一次。这种在缓冲时去除重复数据对于避免不必要的计算和 DOM 操作是非常重要的。然后,在下一个的事件循环 tick 中,Vue 刷新队列并执行实际(已去重的)工作。
v-show 与 v-if 有什么区别?
v-if 是真正的条件渲染,因为它会确保在切换过程中条件块内的事件监听器和子组件适当地被销毁和重建;也是惰性的:如果在初始渲染时条件为假,则什么也不做——直到条件第一次变为真时,才会开始渲染条件块。
v-show 就简单得多——不管初始条件是什么,元素总是会被渲染,并且只是简单地基于 CSS 的 “display” 属性进行切换。
所以,v-if 适用于在运行时很少改变条件,不需要频繁切换条件的场景;v-show 则适用于需要非常频繁切换条件的场景。
Vue 组件如何通信?
Vue 组件通信的方法如下:
props/$emit+v-on
: 通过 props 将数据自上而下传递,而通过 $emit 和 v-on 来向上传递信息。EventBus: 通过 EventBus 进行信息的发布与订阅
vuex: 是全局数据管理库,可以通过 vuex 管理全局的数据流
$attrs/$listeners
: Vue2.4 中加入的$attrs/$listeners
可以进行跨级的组件通信provide/inject:以允许一个祖先组件向其所有子孙后代注入一个依赖,不论组件层次有多深,并在起上下游关系成立的时间里始终生效,这成为了跨组件通信的基础
还有一些用 solt 插槽或者 ref 实例进行通信的,使用场景过于有限就不赘述了。
参考:前端vue面试题详细解答
谈一下对 vuex 的个人理解
vuex 是专门为 vue 提供的全局状态管理系统,用于多个组件中数据共享、数据缓存等。(无法持久化、内部核心原理是通过创造一个全局实例 new Vue)
主要包括以下几个模块:
State:定义了应用状态的数据结构,可以在这里设置默认的初始状态。
Getter:允许组件从 Store 中获取数据,mapGetters 辅助函数仅仅是将 store 中的 getter 映射到局部计算属性。
Mutation:是唯一更改 store 中状态的方法,且必须是同步函数。
Action:用于提交 mutation,而不是直接变更状态,可以包含任意异步操作。
Module:允许将单一的 Store 拆分为多个 store 且同时保存在单一的状态树中。
vue 的优点
轻量级框架:只关注视图层,是一个构建数据的视图集合,大小只有几十 kb;
简单易学:国人开发,中文文档,不存在语言障碍 ,易于理解和学习;
双向数据绑定:保留了 angular 的特点,在数据操作方面更为简单;
组件化:保留了 react 的优点,实现了 html 的封装和重用,在构建单页面应用方面有着独特的优势;
视图,数据,结构分离:使数据的更改更为简单,不需要进行逻辑代码的修改,只需要操作数据就能完成相关操作;
虚拟 DOM:dom 操作是非常耗费性能的,不再使用原生的 dom 操作节点,极大解放 dom 操作,但具体操作的还是 dom 不过是换了另一种方式;
运行速度更快:相比较与 react 而言,同样是操作虚拟 dom,就性能而言,vue 存在很大的优势。
Vue 的生命周期方法有哪些 一般在哪一步发请求
beforeCreate 在实例初始化之后,数据观测(data observer) 和 event/watcher 事件配置之前被调用。在当前阶段 data、methods、computed 以及 watch 上的数据和方法都不能被访问
created 实例已经创建完成之后被调用。在这一步,实例已完成以下的配置:数据观测(data observer),属性和方法的运算, watch/event 事件回调。这里没有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 中有助于一致性;
Vue 组件间通信有哪几种方式?
Vue 组件间通信是面试常考的知识点之一,这题有点类似于开放题,你回答出越多方法当然越加分,表明你对 Vue 掌握的越熟练。Vue 组件间通信只要指以下 3 类通信:父子组件通信、隔代组件通信、兄弟组件通信,下面我们分别介绍每种通信方式且会说明此种方法可适用于哪类组件间通信。
(1)props / $emit
适用 父子组件通信
这种方法是 Vue 组件的基础,相信大部分同学耳闻能详,所以此处就不举例展开介绍。
(2)ref
与 $parent / $children
适用 父子组件通信
ref
:如果在普通的 DOM 元素上使用,引用指向的就是 DOM 元素;如果用在子组件上,引用就指向组件实例$parent
/$children
:访问父 / 子实例
(3)EventBus ($emit / $on)
适用于 父子、隔代、兄弟组件通信
这种方法通过一个空的 Vue 实例作为中央事件总线(事件中心),用它来触发事件和监听事件,从而实现任何组件间的通信,包括父子、隔代、兄弟组件。
(4)$attrs
/$listeners
适用于 隔代组件通信
$attrs
:包含了父作用域中不被 prop 所识别 (且获取) 的特性绑定 ( class 和 style 除外 )。当一个组件没有声明任何 prop 时,这里会包含所有父作用域的绑定 ( class 和 style 除外 ),并且可以通过v-bind="$attrs"
传入内部组件。通常配合 inheritAttrs 选项一起使用。$listeners
:包含了父作用域中的 (不含 .native 修饰器的) v-on 事件监听器。它可以通过v-on="$listeners"
传入内部组件
(5)provide / inject
适用于 隔代组件通信
祖先组件中通过 provider 来提供变量,然后在子孙组件中通过 inject 来注入变量。 provide / inject API 主要解决了跨级组件间的通信问题,不过它的使用场景,主要是子组件获取上级组件的状态,跨级组件间建立了一种主动提供与依赖注入的关系。
(6)Vuex 适用于 父子、隔代、兄弟组件通信
Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式。每一个 Vuex 应用的核心就是 store(仓库)。“store” 基本上就是一个容器,它包含着你的应用中大部分的状态 ( state )。
Vuex 的状态存储是响应式的。当 Vue 组件从 store 中读取状态的时候,若 store 中的状态发生变化,那么相应的组件也会相应地得到高效更新。
改变 store 中的状态的唯一途径就是显式地提交 (commit) mutation。这样使得我们可以方便地跟踪每一个状态的变化。
如何定义动态路由?如何获取传过来的动态参数?
(1)param 方式
配置路由格式:
/router/:id
传递的方式:在 path 后面跟上对应的值
传递后形成的路径:
/router/123
1)路由定义
2)路由跳转
3)参数获取通过 $route.params.userid
获取传递的值
(2)query 方式
配置路由格式:
/router
,也就是普通配置传递的方式:对象中使用 query 的 key 作为传递方式
传递后形成的路径:
/route?id=123
1)路由定义
2)跳转方法
3)获取参数
实际工作中,你总结的 vue 最佳实践有哪些
从编码风格、性能、安全等方面说几条:
编码风格方面:
命名组件时使用“多词”风格避免和
HTML
元素冲突使用“细节化”方式定义属性而不是只有一个属性名
属性名声明时使用“驼峰命名”,模板或
jsx
中使用“肉串命名”使用
v-for
时务必加上key
,且不要跟v-if
写在一起
性能方面:
路由懒加载减少应用尺寸
利用
SSR
减少首屏加载时间利用
v-once
渲染那些不需要更新的内容一些长列表可以利用虚拟滚动技术避免内存过度占用
对于深层嵌套对象的大数组可以使用
shallowRef
或shallowReactive
降低开销避免不必要的组件抽象
安全:
不使用不可信模板,例如使用用户输入拼接模板:
template: <div> + userProvidedString + </div>
避免使用
v-html
,:url
,:style
等,避免html
、url
、样式等注入
如果让你从零开始写一个 vue 路由,说说你的思路
思路分析:
首先思考vue
路由要解决的问题:用户点击跳转链接内容切换,页面不刷新。
借助
hash
或者 history api
实现url
跳转页面不刷新同时监听
hashchange
事件或者popstate
事件处理跳转根据
hash
值或者state
值从routes
表中匹配对应component
并渲染
回答范例:
一个SPA
应用的路由需要解决的问题是 页面跳转内容改变同时不刷新 ,同时路由还需要以插件形式存在,所以:
首先我会定义一个
createRouter
函数,返回路由器实例,实例内部做几件事
保存用户传入的配置项
监听
hash
或者popstate
事件回调里根据
path
匹配对应路由
将
router
定义成一个Vue
插件,即实现install
方法,内部做两件事
实现两个全局组件:
router-link
和router-view
,分别实现页面跳转和内容显示定义两个全局变量:
$route
和$router
,组件内可以访问当前路由和路由器实例
Vue 中 v-html 会导致哪些问题
可能会导致
xss
攻击v-html
会替换掉标签内部的子元素
computed 的实现原理
computed 本质是一个惰性求值的观察者。
computed 内部实现了一个惰性的 watcher,也就是 computed watcher,computed watcher 不会立刻求值,同时持有一个 dep 实例。
其内部通过 this.dirty 属性标记计算属性是否需要重新求值。
当 computed 的依赖状态发生改变时,就会通知这个惰性的 watcher,
computed watcher 通过 this.dep.subs.length 判断有没有订阅者,
有的话,会重新计算,然后对比新旧值,如果变化了,会重新渲染。 (Vue 想确保不仅仅是计算属性依赖的值发生变化,而是当计算属性最终计算的值发生变化时才会触发渲染 watcher 重新渲染,本质上是一种优化。)
没有的话,仅仅把 this.dirty = true。 (当计算属性依赖于其他数据时,属性并不会立即重新计算,只有之后其他地方需要读取属性的时候,它才会真正计算,即具备 lazy(懒计算)特性。)
了解 nextTick 吗?
异步方法,异步渲染最后一步,与 JS 事件循环联系紧密。主要使用了宏任务微任务(setTimeout
、promise
那些),定义了一个异步方法,多次调用nextTick
会将方法存入队列,通过异步方法清空当前队列。
ref 和 reactive 异同
这是Vue3
数据响应式中非常重要的两个概念,跟我们写代码关系也很大
ref
接收内部值(inner value
)返回响应式Ref
对象,reactive
返回响应式代理对象从定义上看
ref
通常用于处理单值的响应式,reactive
用于处理对象类型的数据响应式两者均是用于构造响应式数据,但是
ref
主要解决原始值的响应式问题ref
返回的响应式数据在 JS 中使用需要加上.value
才能访问其值,在视图中使用会自动脱ref
,不需要.value
;ref
可以接收对象或数组等非原始值,但内部依然是reactive
实现响应式;reactive
内部如果接收Re
f 对象会自动脱ref
;使用展开运算符(...
)展开reactive
返回的响应式对象会使其失去响应性,可以结合toRefs()
将值转换为Ref
对象之后再展开。reactive
内部使用Proxy
代理传入对象并拦截该对象各种操作,从而实现响应式。ref
内部封装一个RefImpl
类,并设置get value/set value
,拦截用户对值的访问,从而实现响应式
vue-router 守卫
导航守卫
router.beforeEach
全局前置守卫
to: Route
: 即将要进入的目标(路由对象)from: Route
: 当前导航正要离开的路由next: Function
: 一定要调用该方法来resolve
这个钩子。(一定要用这个函数才能去到下一个路由,如果不用就拦截)执行效果依赖 next 方法的调用参数。
next()
: 进行管道中的下一个钩子。如果全部钩子执行完了,则导航的状态就是 confirmed (确认的)。next(false)
:取消进入路由,url 地址重置为 from 路由地址(也就是将要离开的路由地址)
路由独享的守卫 你可以在路由配置上直接定义
beforeEnter
守卫
组件内的守卫你可以在路由组件内直接定义以下路由导航守卫
Vue 中常见性能优化
编码优化 :
使用
v-show
复用DOM
:避免重复创建组件
合理使用路由懒加载、异步组件,有效拆分
App
尺寸,访问时才异步加载
keep-alive
缓存页面:避免重复创建组件实例,且能保留缓存组件状态
v-once
和v-memo
:不再变化的数据使用v-once
按条件跳过更新时使用v-momo
:下面这个列表只会更新选中状态变化项
长列表性能优化:如果是大数据长列表,可采用虚拟滚动,只渲染少部分区域的内容
防止内部泄漏,组件销毁后把全局变量和事件销毁:
Vue
组件销毁时,会自动解绑它的全部指令及事件监听器,但是仅限于组件本身的事件
图片懒加载
对于图片过多的页面,为了加速页面加载速度,所以很多时候我们需要将页面内未出现在可视区域内的图片先不做加载,等到滚动到可视区域后再去加载
滚动到可视区域动态加载
https://tangbc.github.io/vue-virtual-scroll-list(opens new window)
第三方插件按需引入:(
babel-plugin-component
)
像element-plus
这样的第三方组件库可以按需引入避免体积太大
服务端渲染:SSR
如果SPA
应用有首屏渲染慢的问题,可以考虑SSR
以及下面的其他方法
不要将所有的数据都放在
data
中,data
中的数据都会增加getter
和setter
,会收集对应的watcher
v-for
遍历为item
添加key
v-for
遍历避免同时使用v-if
区分
computed
和watch
的使用拆分组件(提高复用性、增加代码的可维护性,减少不必要的渲染 )
防抖、节流
用户体验
app-skeleton
骨架屏pwa
serviceworker
SEO 优化
预渲染插件
prerender-spa-plugin
服务端渲染
ssr
打包优化
Webpack
对图片进行压缩使用
cdn
的方式加载第三方模块多线程打包
happypack
splitChunks
抽离公共文件优化
SourceMap
构建结果输出分析,利用
webpack-bundle-analyzer
可视化分析工具
基础的 Web 技术的优化
服务端
gzip
压缩浏览器缓存
CDN
的使用使用
Chrome Performance
查找性能瓶颈
Vue 怎么用 vm.$set() 解决对象新增属性不能响应的问题 ?
受现代 JavaScript 的限制 ,Vue 无法检测到对象属性的添加或删除。由于 Vue 会在初始化实例时对属性执行 getter/setter 转化,所以属性必须在 data 对象上存在才能让 Vue 将它转换为响应式的。但是 Vue 提供了 Vue.set (object, propertyName, value) / vm.$set (object, propertyName, value)
来实现为对象添加响应式属性,那框架本身是如何实现的呢?
我们查看对应的 Vue 源码:vue/src/core/instance/index.js
我们阅读以上源码可知,vm.$set 的实现原理是:
如果目标是数组,直接使用数组的 splice 方法触发相应式;
如果目标是对象,会先判读属性是否存在、对象是否是响应式,最终如果要对属性进行响应式处理,则是通过调用 defineReactive 方法进行响应式处理( defineReactive 方法就是 Vue 在初始化对象时,给对象属性采用 Object.defineProperty 动态添加 getter 和 setter 的功能所调用的方法)
Vue3 速度快的原因
Vue3.0 性能提升体现在哪些方面
代码层面性能优化主要体现在全新响应式
API
,基于Proxy
实现,初始化时间和内存占用均大幅改进;编译层面做了更多编译优化处理,比如
静态标记pachFlag
(diff
算法增加了一个静态标记,只对比有标记的dom
元素)、事件增加缓存
、静态提升
(对不参与更新的元素,会做静态提升,只会被创建一次,之后会在每次渲染时候被不停的复用)等,可以有效跳过大量diff
过程;打包时更好的支持
tree-shaking
,因此整体体积更小,加载更快ssr
渲染以字符串方式渲染
一、编译阶段
试想一下,一个组件结构如下图
可以看到,组件内部只有一个动态节点,剩余一堆都是静态节点,所以这里很多 diff
和遍历其实都是不需要的,造成性能浪费
因此,Vue3 在编译阶段,做了进一步优化。主要有如下:
diff
算法优化静态提升
事件监听缓存
SSR
优化
1. diff 算法优化
Vue 2x
中的虚拟dom
是进行全量的对比。Vue 3x
中新增了静态标记(PatchFlag
):在与上次虚拟结点进行对比的时候,值对比 带有patch flag
的节点,并且可以通过flag
的信息得知当前节点要对比的具体内容化
Vue2.x 的 diff 算法
vue2.x
的diff
算法叫做全量比较
,顾名思义,就是当数据改变的时候,会从头到尾的进行vDom
对比,即使有些内容是永恒固定不变的
Vue3.0 的 diff 算法
vue3.0
的diff
算法有个叫静态标记(PatchFlag
)的小玩意,啥是静态标记呢?简单点说,就是如果你的内容会变,我会给你一个flag
,下次数据更新的时候我直接来对比你,我就不对比那些没有标记的了
已经标记静态节点的p
标签在diff
过程中则不会比较,把性能进一步提高
关于静态类型枚举如下
2. hoistStatic 静态提升
Vue 2x
: 无论元素是否参与更新,每次都会重新创建。Vue 3x
: 对不参与更新的元素,会做静态提升,只会被创建一次,之后会在每次渲染时候被不停的复用。这样就免去了重复的创建节点,大型应用会受益于这个改动,免去了重复的创建操作,优化了运行时候的内存占用
开启静态提升前
开启静态提升后编译结果
可以看到开启了静态提升后,直接将那两个内容为helloworld
的p
标签声明在外面了,直接就拿来用了。同时 _hoisted_1
和_hoisted_2
被打上了 PatchFlag
,静态标记值为 -1
,特殊标志是负整数表示永远不会用于 Diff
3. cacheHandlers 事件监听缓存
默认情况下 绑定事件会被视为动态绑定 ,所以每次都会去追踪它的变化
但是因为是同一个函数,所以没有追踪变化,直接缓存起来复用即可
开启事件侦听器缓存之前:
这里有一个8
,表示着这个节点有了静态标记,有静态标记就会进行diff
算法对比差异,所以会浪费时间
开启事件侦听器缓存之后:
上述发现开启了缓存后,没有了静态标记。也就是说下次diff
算法的时候直接使用
4. SSR 优化
当静态内容大到一定量级时候,会用createStaticVNode
方法在客户端去生成一个static node
,这些静态node
,会被直接innerHtml
,就不需要创建对象,然后根据对象渲染
编译后
二、源码体积
相比Vue2
,Vue3
整体体积变小了,除了移出一些不常用的API
,再重要的是Tree shanking
任何一个函数,如ref
、reactive
、computed
等,仅仅在用到的时候才打包,没用到的模块都被摇掉,打包的整体体积变小
三、响应式系统
vue2
中采用 defineProperty
来劫持整个对象,然后进行深度遍历所有属性,给每个属性添加getter
和setter
,实现响应式
vue3
采用proxy
重写了响应式系统,因为proxy
可以对整个对象进行监听,所以不需要深度遍历
可以监听动态属性的添加
可以监听到数组的索引和数组
length
属性可以监听删除属性
说下你的 vue 项目的目录结构,如果是大型项目你该怎么划分结构和划分组件呢
一、为什么要划分
使用vue
构建项目,项目结构清晰会提高开发效率,熟悉项目的各种配置同样会让开发效率更高
在划分项目结构的时候,需要遵循一些基本的原则:
文件夹和文件夹内部文件的语义一致性
单一入口/出口
就近原则,紧耦合的文件应该放到一起,且应以相对路径引用
公共的文件应该以绝对路径的方式从根目录引用
/src
外的文件不应该被引入
文件夹和文件夹内部文件的语义一致性
我们的目录结构都会有一个文件夹是按照路由模块来划分的,如pages
文件夹,这个文件夹里面应该包含我们项目所有的路由模块,并且仅应该包含路由模块,而不应该有别的其他的非路由模块的文件夹
这样做的好处在于一眼就从 pages
文件夹看出这个项目的路由有哪些
单一入口/出口
举个例子,在pages
文件夹里面存在一个seller
文件夹,这时候seller
文件夹应该作为一个独立的模块由外部引入,并且 seller/index.js
应该作为外部引入 seller 模块的唯一入口
这样做的好处在于,无论你的模块文件夹内部有多乱,外部引用的时候,都是从一个入口文件引入,这样就很好的实现了隔离,如果后续有重构需求,你就会发现这种方式的优点
就近原则,紧耦合的文件应该放到一起,且应以相对路径引用
使用相对路径可以保证模块内部的独立性
举个例子
假设我们现在的 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
外会放一些项目配置,依赖,环境等文件
这样的好处是方便划分项目代码文件和配置文件
二、目录结构
单页面目录结构
多页面目录结构
小结
项目的目录结构很重要,因为目录结构能体现很多东西,怎么规划目录结构可能每个人有自己的理解,但是按照一定的规范去进行目录的设计,能让项目整个架构看起来更为简洁,更加易用
评论