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