
点击上方蓝字关注我们

基础知识
MVVM的理解
MVVM
是一种软件架构模式,MVVM
分为 Model
、View
、ViewModel
:
-
Model
代表数据模型,数据和业务逻辑都在Model
层中定义; -
View
代表UI视图,负责数据的展示; -
ViewModel
负责监听Model
中数据的改变并且控制视图的更新,处理用户交互操作;
Model
和View
并无直接关联,而是通过ViewModel
来进行联系的,Model
和ViewModel
之间有着双向数据绑定的联系。因此当Model
中的数据改变时会触发View
层的刷新,View
中由于用户交互操作而改变的数据也会在Model
中同步。
vue和react的区别,有什么相同
不同:
-
模版语法不同,
react
采用JSX语法,vue
使用基于HTML的模版语法 -
数据绑定不同,
vue
使用双向数据绑定,react
则需要手动控制组件的状态和属性。 -
状态管理不同,
vue
使用vuex
状态管理,react
使用redux
状态管理 -
组件通信不同,
vue
使用props
和事件的方式进行父子组件通信,react
则通过props
和回调函数的方式进行通信。 -
生命周期不同,
vue
有8个生命周期钩子,react
有10个 -
响应式原理不同,
vue
使用双向绑定来实现数据更新,react
则通过单向数据流来实现
相同:
-
组件化开发:
Vue
和React
都采用了组件化开发的方式,将用户界面划分为独立、可复用的组件,从而使代码更加模块化、可维护和可扩展。 -
虚拟 DOM:
Vue
和React
都使用虚拟 DOM 技术,通过在JavaScript
和真实 DOM 之间建立一个轻量级的虚拟 DOM 层,实现高效的 DOM 更新和渲染。 -
响应式更新:
Vue
和React
都支持响应式更新,即当数据发生变化时,会自动更新相关的组件和视图,以保持用户界面的同步性。 -
集成能力:
Vue
和React
都具有良好的集成能力,可以与其他库和框架进行整合,例如Vue
可以与Vuex
、Vue Router
等配套使用,React
可以与Redux
、React Router
等配套使用。
Vue2和Vue3有哪些区别
-
Vue2
使用的是optionsAPI
,Vue3
使用composition API
,更好的组织代码,提高代码可维护性 -
Vue3
使用Proxy
代理实现了新的响应式系统,比Vue2
有着更好的性能和更准确的数据变化追踪能力。 -
Vue3
引入了Teleprot组件,可以将DOM元素渲染到DOM数的其他位置,用于创建模态框、弹出框等。 -
Vue3
全局API名称发生了变化,同时新增了watchEffect
、Hooks
等功能 -
Vue3
对TypeScript
的支持更加友好 -
Vue3
核心库的依赖更少,减少打包体积 -
3支持更好的
Tree Shanking
,可以更加精确的按需要引入模块
SPA的理解,有什么优缺点
SPA(单页应用)是一种前端应用程序的架构模式,它通过在加载应用程序时只加载单个 HTML
页面,并通过使用 JavaScript
动态地更新页面内容,从而实现无刷新的用户体验。
SPA和多页面有什么区别
区别
-
页面加载方式:在多页面应用中,每个页面都是独立的 HTML 文件,每次导航时需要重新加载整个页面。而在
SPA
中,初始加载时只加载一个 HTML 页面,后续的导航通过JavaScript
动态地更新页面内容,无需重新加载整个页面。 -
用户体验:
SPA
提供了流畅、快速的用户体验,因为页面切换时无需等待整个页面的重新加载,只有需要的数据和资源会被加载,减少了页面刷新的延迟。多页面应用则可能会有页面刷新的延迟,给用户带来较长的等待时间。 -
代码复用:
SPA
通常采用组件化开发的方式,可以在不同的页面中复用组件,提高代码的可维护性和可扩展性。多页面应用的每个页面都是独立的,组件复用的机会较少。 -
路由管理:在多页面应用中,页面之间的导航和路由由服务器处理,每个页面对应一个不同的
URL
。而在SPA
中,前端负责管理页面的导航和路由,通过前端路由库(如React Router
或Vue Router
)来管理不同路径对应的组件。 -
SEO(搜索引擎优化):由于多页面应用的每个页面都是独立的 HTML 文件,搜索引擎可以直接索引和抓取每个页面的内容,有利于搜索引擎优化。相比之下,
SPA
的内容是通过JavaScript
动态生成的,搜索引擎的爬虫可能无法正确地获取和索引页面的内容,需要采取额外的优化措施。 -
服务器负载:
SPA
只需初始加载时获取HTML
、CSS
和JavaScript
文件,后续的页面更新和数据获取通常通过 API 请求完成,减轻了服务器的负载。而多页面应用每次导航都需要从服务器获取整个页面的内容。
优点
-
用户体验:
SPA
提供了流畅、快速的用户体验,在页面加载后,只有需要的数据和资源会被加载,减少了页面刷新的延迟。 -
响应式交互:由于
SPA
依赖于异步数据加载和前端路由,可以实现实时更新和动态加载内容,使用户可以快速地与应用程序交互。 -
代码复用:
SPA
通常采用组件化开发的方式,提高了代码的可维护性和可扩展性。 -
服务器负载较低:由于只有初始页面加载时需要从服务器获取
HTML
、CSS
和JavaScript
文件,减轻了服务器的负载。
缺点:
-
首次加载时间:
SPA
首次加载时需要下载较大的JavaScript
文件,这可能导致初始加载时间较长。 -
SEO(搜索引擎优化)问题:由于
SPA
的内容是通过JavaScript
动态生成的,搜索引擎的爬虫可能无法正确地获取和索引页面的内容。 -
内存占用:
SPA
在用户浏览应用程序时保持单个页面的状态,这可能导致较高的内存占用。 -
安全性:由于
SPA
通常使用API
进行数据获取,因此需要特别注意安全性。
Vue的性能优化有哪些
编码阶段
-
v-if
和v-for
不一起使用 -
v-for
保证key
的唯一性 -
使用
keep-alive
缓存组件 -
v-if
和v-show
酌情使用 -
路由懒加载、异步组件
-
图片懒加载
-
节流防抖
-
第三方模块按需引入
-
服务端与渲染
打包优化
-
压缩代码
-
使用CDN加载第三方模块
-
抽离公共文件
用户体验
-
骨架屏
-
客户端缓存
SEO优化
-
预渲染
-
服务端渲染
-
合理使用
meta
标签
Vue生命周期
创建前后:
-
beforeCreate(创建前):
数据观测和初始化事件还未开始,不能访问data
、computed
、watch
、methods
上的数据方法。 -
created(创建后):
实例创建完成,可以访问data
、computed
、watch
、methods
上的数据方法,但此时渲染节点还未挂在到DOM上,所以不能访问。
挂载前后:
-
beforeMount(挂载前):
Vue实例还未挂在到页面HTML上,此时可以发起服务器请求 -
mounted(挂载后):
Vue实例已经挂在完毕,可以操作DOM
更新前后:
-
beforeUpdate(更新前):
数据更新之前调用,还未渲染页面 -
updated(更新后):
DOM重新渲染,此时数据和界面都是新的。
销毁前后:
-
beforeDestorye(销毁前):
实例销毁前调用,这时候能够获取到this
-
destoryed(销毁后):
实例销毁后调用,实例完全被销毁。
常用的属性、指令有哪些
属性:
-
data
:用于定义组件的初始数据。 -
props
:用于传递数据给子组件。 -
computed
:用于定义计算属性。 -
methods
:用于定义组件的方法。 -
watch
:用于监听组件的数据变化。 -
components
:用于注册子组件。可以通过components
属性将其他组件注册为当前组件的子组件,从而在模板中使用这些子组件。
指令:
-
v-if
:条件渲染指令,根据表达式的真假来决定是否渲染元素。 -
v-show
:条件显示指令,根据表达式的真假来决定元素的显示和隐藏。 -
v-for
:列表渲染指令,用于根据数据源循环渲染元素列表。 -
v-bind
:属性绑定指令,用于动态绑定元素属性到Vue
实例的数据。 -
v-on
:事件绑定指令,用于监听DOM
事件,并执行对应的Vue
方法。 -
v-model
:双向数据绑定指令,用于在表单元素和Vue
实例的数据之间建立双向绑定关系。 -
v-text
:文本插值指令,用于将数据插入到元素的文本内容中。 -
v-html
:HTML
插值指令,用于将数据作为HTML
解析并插入到元素中。
Computed 和 Watch 的区别
computed
计算属性,通过对已有的属性值进行计算得到一个新值。它需要依赖于其他的数据,当数据发生变化时,computed
会自动计算更新。computed
属性值会被缓存,只有当依赖数据发生变化时才会重新计算,这样可以避免重复计算提高性能。
watch
用于监听数据的变化,并在变化时执行一些操作。它可以监听单个数据或者数组,当数据发生变化时会执行对应的回调函数,和computed
不同的是watch
不会有缓存。
Vue组件通信
父传子
-
props
-
$children
-
$refs
子传父
-
$emit
-
$parent
兄弟组件
-
provied
-
inject
-
eventBus
-
Vuex
常见的事件修饰符及其作用
-
.stop
阻止冒泡 -
.prevent
阻止默认事件 -
.capture
:与事件冒泡的方向相反,事件捕获由外到内; -
.
self
:只会触发自己范围内的事件,不包含子元素; -
.once
:只会触发一次。
v-if和v-show的区别
v-if
元素不可见,直接删除DOM,有更高的切换消耗。v-show
通过设置元素display: none
控制显示隐藏,更高的初始渲染消耗。
v-html 的原理
会先移除节点下的所有节点,调用html
方法,通过addProp
添加innerHTML
属性,归根结底还是设置innerHTML
为v-html
的值。
v-model 是如何实现的,语法糖实际是什么?
Vue 中数据双向绑定是一个指令v-model
,可以绑定一个响应式数据到视图,同时视图的变化能改变该值。
-
当作用在表单上:通过
v-bind:value
绑定数据,v-on:input
来监听数据变化并修改value
-
当作用在组件上:本质上是一个父子通信语法糖,通过
props
和$emit
实现。
data为什么是一个函数而不是对象
因为对象是一个引用类型,如果data
是一个对象的情况下会造成多个组件共用一个data
,data
为一个函数,每个组件都会有自己的私有数据空间,不会干扰其他组件的运行。
mixin 和 mixins 区别
-
mixin
用于全局混入,会影响到每个组件实例,通常插件都是这样做初始化的。 -
mixins
应该是最常使用的扩展组件的方式了。如果多个组件中有相同的业务逻辑,就可以将这些逻辑剥离出来,通过mixins
混入代码,比如上拉下拉加载数据这种逻辑等等。
路由的hash和history模式的区别
hash模式开发中默认的模式,地址栏URL后携带#
,后面为路由。 原理是通过onhashchange()
事件监听hash
值变化,在页面hash
值发生变化后,window
就可以监听到事件改变,并按照规则加载相应的代码。hash
值变化对应的URL都会被记录下来,这样就能实现浏览器历史页面前进后退。
history模式history
模式中URL没有#
,这样相对hash
模式更好看,但是需要后台配置支持。
history
原理是使用HTML5 history
提供的pushState
、replaceState
两个API,用于浏览器记录历史浏览栈,并且在修改URL时不会触发页面刷新和后台数据请求。
router和route的区别
-
$route
是路由信息,包括path
、params
、query
、name
等路由信息参数 -
$router
是路由实例,包含了路由跳转方法、钩子函数等
如何设置动态路由
-
params传参
-
路由配置:
/index/:id
-
路由跳转:
this.$router.push({name: 'index', params: {id: "zs"}});
-
路由参数获取:
$route.params.id
-
最后形成的路由:
/index/zs
-
query传参
-
路由配置:
/index
正常的路由配置 -
路由跳转:
this.$rouetr.push({path: 'index', query:{id: "zs"}});
-
路由参数获取:
$route.query.id
-
最后形成的路由:
/index?id=zs
区别
-
获取参数方式不一样,一个通过
$route.params
,一个通过$route.query
-
参数的生命周期不一样,
query
参数在URL地址栏中显示不容易丢失,params
参数不会在地址栏显示,刷新后会消失
路由守卫
-
全局前置钩子:
beforeEach
、beforeResolve
、afterEach
-
路由独享守卫:
beforeEnter
-
组件内钩子:
beforeRouterEnter
、beforeRouterUpdate
、beforeRouterLeave
Vue中key的作用
key
的作用主要是为了高效的更新虚拟DOM,其原理是vue
在patch
过程中通过key
可以精准判断两个节点是否是同一个,从而避免频繁更新不同元素,减少DOM
操作量,提高性能。
为什么不建议用index作为key?
如果将数组下标作为key
值,那么当列表发生变化时,可能会导致key
值发生改变,从而引发不必要的组件重新渲染,甚至会导致性能问题。例如,当删除列表中某个元素时,其后面的所有元素的下标都会发生改变,导致Vue
重新渲染整个列表。
为什么v-for和v-if不能一起使用
v-for
比v-if
优先级更高,一起使用的话每次渲染列表时都要执行一次条件判断,造成不必要的计算,影响性能。
原理知识
双向数据绑定的原理
采用数据劫持结合发布者-订阅者模式的方式,data
数据在初始化的时候,会实例化一个Observe
类,在它会将data
数据进行递归遍历,并通过Object.defineProperty
方法,给每个值添加上一个getter
和一个setter
。在数据读取的时候会触发getter
进行依赖(Watcher)收集,当数据改变时,会触发setter
,对刚刚收集的依赖进行触发,并且更新watcher
通知视图进行渲染。
使用 Object.defineProperty() 来进行数据劫持有什么缺点?
该方法只能监听到数据的修改,监听不到数据的新增和删除,从而不能触发组件更新渲染。vue2中会对数组的新增删除方法push、pop、shift、unshift、splice、sort、reserve
通过重写的形式,在拦截里面进行手动收集触发依赖更新。
和Vue3相比有什么区别?
Vue3
采用了Proxy
代理的方式,Proxy
是ES6引入的一个新特性,它提供了一个用于创建代理对象的构造函数。它是对整个对象的监听和拦截,可以对对象所有操作进行处理。而Object.defineProperty
只能监听单个属性的读写,无法监听新增、删除等操作。
Vue是如何收集依赖的?
依赖收集发生在defineReactive()
方法中,在方法内new Dep()
实例化一个Dep()
实例,然后在getter
中通过dep.depend()
方法对数据依赖进行收集,然后在settter
中通过dep.notify()
通知更新。整个Dep
其实就是一个观察者,吧收集的依赖存储起来,在需要的时候进行调用。在收集数据依赖的时候,会为数据创建一个Watcher
,当数据发生改变通知每个Watcher
,由Wathcer
进行更新渲染。
slot是什么?有什么作用?原理是什么?
slot
插槽,一般在封装组件的时候使用,在组件内不知道以那种形式来展示内容时,可以用slot
来占据位置,最终展示形式由父组件以内容形式传递过来,主要分为三种:
-
默认插槽:又名匿名插槽,当
slot
没有指定name
属性值的时候一个默认显示插槽,一个组件内只有有一个匿名插槽。 -
具名插槽:带有具体名字的插槽,也就是带有
name
属性的slot
,一个组件可以出现多个具名插槽。 -
作用域插槽:默认插槽、具名插槽的一个变体,可以是匿名插槽,也可以是具名插槽,该插槽的不同点是在子组件渲染作用域插槽时,可以将子组件内部的数据传递给父组件,让父组件根据子组件的传递过来的数据决定如何渲染该插槽。
实现原理:当子组件vm
实例化时,获取到父组件传入的slot
标签的内容,存放在vm.$slot
中,默认插槽为vm.$slot.default
,具名插槽为vm.$slot.xxx
,xxx 为插槽名,当组件执行渲染函数时候,遇到slot
标签,使用$slot
中的内容进行替换,此时可以为插槽传递数据,若存在数据,则可称该插槽为作用域插槽。
对keep-alive的理解,它是如何实现的,具体缓存的是什么?
keep-alive
是Vue.js的一个内置组件。它能够将不活动的组件实例保存在内存中,而不是直接将其销毁,它是一个抽象组件,不会被渲染到真实DOM中,也不会出现在父组件链中。
-
include
字符串或正则表达式,只有名称匹配的组件会被匹配; -
exclude
字符串或正则表达式,任何名称匹配的组件都不会被缓存; -
max
数字,最多可以缓存多少组件实例。
2 个生命周期 activated , deactivated
-
activated
:当缓存的组件被激活时,该钩子函数被调用。可以在该钩子函数中进行一些状态恢复、数据更新等操作。 -
deactivated
:当缓存的组件被停用时,该钩子函数被调用。可以在该钩子函数中进行一些状态保存、数据清理等操作。
keep-alive
内部其实是一个函数式组件,没有template
标签。在render
中通过获取组件的name
和include、exclude
进行匹配。匹配不成功,则不需要进行缓存,直接返回该组件的vnode
。
匹配成功就进行缓存,获取组件的key
在cache
中进行查找,如果存在,则将他原来位置上的 key
给移除,同时将这个组件的 key
放到数组最后面(LRU
)也就实现了max
功能。
不存在的话,就需要对组件进行缓存。将当前组件push(key)
添加到尾部,然后再判断当前缓存的max
是否超出指定个数,如果超出直接将第一个组件销毁(缓存淘汰策略LRU)。
LRU(Least recently used,最近最少使用)算法根据数据的历史访问记录来进行淘汰数据,其核心思想是“如果数据最近被访问过,那么将来被访问的几率也更高”。
$nextTick 原理及作用
Vue 的 nextTick
其本质是对 JavaScript
执行原理 EventLoop
的一种应用。nextTick
是将回调函数放到一个异步队列中,保证在异步更新DOM的watcher
后面,从而获取到更新后的DOM。
因为在created()
钩子函数中,页面的DOM还未渲染,这时候也没办法操作DOM,所以,此时如果想要操作DOM,必须将操作的代码放在nextTick()
的回调函数中。
Vue模版编译原理
模版编译主要过程:template ---> ast ---> render
,分别对象三个方法
-
parse
函数解析template
-
optimize
函数优化静态内容 -
generate
函数创建render
函数字符串
调用parse
方法,将template
转化为AST
(抽象语法树),AST
定义了三种类型,一种html
标签,一种文本,一种插值表达式,并且通过 children
这个字段层层嵌套形成了树状的结构。
optimize
方法对AST
树进行静态内容优化,分析出哪些是静态节点,给其打一个标记,为后续更新渲染可以直接跳过静态节点做优化。
generate
将AST
抽象语法树编译成 render
字符串,最后通过new Function(render)
生成可执行的render
函数
Vuex
Vuex 的原理
Vuex
是专门为Vue
设计的状态管理,当Vue
从store
中读取数据后,数据发生改变,组件中的数据也会发生变化。
-
Vue Components
负责接收用户操作交互行为,执行dispatch触发对应的action进行回应 -
dispatch
唯一能执行action的方法 -
action
用来接收components的交互行为,包含异步同步操作 -
commit
对mutation进行提交,唯一能执行mutation的方法 -
mutation
唯一可以修改state状态的方法 -
state
页面状态管理容器,用于存储状态 -
getters
读取state方法
Vue组件接收交互行为,调用dispatch
方法触发action
相关处理,若页面状态需要改变,则调用commit
方法提交mutation
修改state
,通过getters
获取到state
新值,重新渲染Vue Components
,界面随之更新。
Vuex中action和mutation的区别
-
mutation
更专注于修改state
,必须是同步执行。 -
action
提交的是mutation
,而不是直接更新数据,可以是异步的,如业务代码,异步请求。 -
action
可以包含多个mutation
Vuex 和 localStorage 的区别
-
Vuex
存储在内存中,页面关闭刷新就会消失。而localstorage
存储在本地,读取内存比读取硬盘速度要快 -
Vuex
应用于组件之间的传值,localstorage
主要用于不同页面之间的传递 -
Vuex
是响应式的,localstorage
需要刷新
虚拟DOM
对虚拟DOM的理解
虚拟DOM就是用JS对象来表述DOM节点,是对真实DOM的一层抽象。可以通过一些列操作使这个棵树映射到真实DOM上。
如在Vue
中,会把代码转换为虚拟DOM,在最终渲染到页面,在每次数据发生变化前,都会缓存一份虚拟DOM,通过diff
算法来对比新旧虚拟DOM记录到一个对象中按需更新,最后创建真实DOM,从而提升页面渲染性能。
虚拟DOM就一定比真实DOM更快吗
虚拟DOM不一定比真实DOM更快,而是在特定情况下可以提供更好的性能。
在复杂情况下,虚拟DOM可以比真实DOM操作更快,因为它是在内存中维护一个虚拟的DOM树,将真实DOM操作转换为对虚拟DOM的操作,然后通过diff
算法找出需要更新的部分,最后只变更这部分到真实DOM就可以。在频繁变更下,它可以批量处理这些变化从而减少对真实DOM的访问和操作,减少浏览器的回流重绘,提高页面渲染性能。
而在一下简单场景下,直接操作真实DOM可能会更快,当更新操作很少或者只是局部改变时,直接操作真实DOM比操作虚拟DOM更高效,省去了虚拟DOM的计算、对比开销。
虚拟DOM的解析过程
-
首先对将要插入到文档中的 DOM 树结构进行分析,使用 js 对象将其表示出来,比如一个元素对象,包含
TagName
、props
和Children
这些属性。然后将这个 js 对象树给保存下来,最后再将 DOM 片段插入到文档中。 -
当页面的状态发生改变,需要对页面的 DOM 的结构进行调整的时候,首先根据变更的状态,重新构建起一棵对象树,然后将这棵新的对象树和旧的对象树进行比较,记录下两棵树的的差异。
-
最后将记录的有差异的地方应用到真正的 DOM 树中去,这样视图就更新了。
DIFF算法原理
diff
的目的是找出差异,最小化的更新视图。diff
算法发生在视图更新阶段,当数据发生变化的时候,diff
会对新旧虚拟DOM进行对比,只渲染有变化的部分。
-
对比是不是同类型标签,不是同类型直接替换
-
如果是同类型标签,执行
patchVnode
方法,判断新旧vnode
是否相等。如果相等,直接返回。 -
新旧
vnode
不相等,需要比对新旧节点,比对原则是以新节点为主,主要分为以下几种。 -
newVnode
和oldVnode
都有文本节点,用新节点替换旧节点。 -
newVnode
有子节点,oldVnode
没有,新增newVnode
的子节点。 -
newVnode
没有子节点,oldVnode
有子节点,删除oldVnode
中的子节点。 -
newVnode
和oldVnode
都有子节点,通过updateChildren
对比子节点。
双端diff
updateChildren
方法用来对比子节点是否相同,将新旧节点同级进行比对,减少比对次数。会创建4个指针,分别指向新旧两个节点的首尾,首和尾指针向中间移动。
每次对比下两个头指针指向的节点、两个尾指针指向的节点,头和尾指向的节点,是不是 key是一样的,也就是可复用的。如果是重复的,直接patch更新一下,如果是头尾节点,需要进行移动位置,结果以新节点的为主。
如果都没有可以复用的节点,就从旧的vnode
中查找,然后进行移动,没有找到就插入一个新节点。
当比对结束后,此时新节点还有剩余,就批量增加,如果旧节点有剩余就批量删除。
原文始发于微信公众号(猿来是前端):前端面试必备八股文合集之Vue
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/250408.html