谈谈你对vue的理解
生命式渲染->组件系统->客户端路由->大规模状态管理->构建工具
声明式框架
命令式和声明式的区别:
- 声明式框架更关注结果,命令时的代码封装到vuejs中,过程靠vuejs实现
- 声明式代码更加简单,不需要关注实现,按照要求填代码就行
- 早期jquery的时代编写的代码都是命令式的,命令式框架重要特点就是关注过程
MVVM模式
映射关系的简化(隐藏controller)
采用虚拟dom
传统更新页面,拼接一个完整的字符串innerHTML全部重新渲染,添加虚拟DOM 后,可以比较新旧虚拟节点,找到变化在进行更新。虚拟 DOM 就是一个对象,用来描述真实 DOM
区分编译时(打包)和运行时(浏览器)
- Vue 的渲染核心就是调用渲染(render)方法将虚拟 DOM 渲染成真实 DOM(缺点就是虚拟 DOM 编写
麻烦) - 专门写个编译时可以将模板编译成虚拟 DOM(在构建的时候进行编译性能更高,不需要再运行的时候进行编译)
组件化
实现高内聚、低耦合、单向数据流
- 组件化开发能大幅提高应用开发效率、测试性、复用性等:
- 降低更新范围,只重新渲染变化的组件
SPA
基本概念
- SPA(single-page application)单页应用,默认情况下我们编写Vue、React 都只有一个htm1 页面,并且提供一个挂载点,最终打包后会再此页面中引入对应的资源。(页面的染全部是由」动态进行渲染的)。切换页面时通过监听路由变化,渲染对应的页面 client side Rendering,客户端渲染 CSR
- MPA(Multi-page application)多页应用,多个htm1页面。每个页面必须重复加载,js,css等相关资源。(服务端返回完整的 html,同时数据也可以再后端进行获取一并返回“模板引擎”)。多页应用跳转需要整页资源刷新。Server SideRendering,服务器端渲染SSR
SPA | MPA | |
---|---|---|
组成 | 一个主页面和页面组件 | 多个完整的页面 |
刷新方式 | 局部刷新 | 整页刷新 |
seo优化 | 无法实现 | 整页实现 |
页面切换 | 速度快,用户体验良好 | 切换加载资源,速度慢,用户体验差 |
维护成本 | 相对容易 | 相对复杂 |
- 户体验好、快,内容的改变不需要重新加载整个页面,服务端压力小。
- SPA 应用不利于搜索引擎的抓取
- 首次渲染速度相对较慢(第一次返回空的 html,需要再次请求首屏数据)白屏时间长。
解决方案
- 静态页面预渲染(Static Site Generatioh) ssG,在构建时生成完整的 html页面。(就是在打包的时候先将页面放到浏览器中运行一下,将HTML保存起来),仅适合静态页面网站。
- 变化率不高的网站SSR+CSR 的方式,首屏采用服务端渲染的方式,后续交互采用客户端染方式。NuxtJS
虚拟DOM
基本概念
基本上所有框架都引入了虚拟 DOM 来对真实 DOM 进行抽象,也就是现在大家所熟知的 VNode 和VDOM
- Virtual DOM 就是用 js 对象来描述真实 DOM,是对真实 DOM 的抽象,由于直接操作 DOM 性能低但是js 层的操作效率高,可以将 DOM 操作转化成对象操作,最终通过 diff 算法比对差异进行更新 DOM(减少了对真实 DOM 的操作)。
- 虚拟 DOM 不依赖真实平台环境从而也可以实现跨平台。
VDOM如何生成
在 vue 中我们常常会为组件编写模板-template
这个模板会被编译器编译为渲染函数-render
在接下来的挂载过程中会调用 render 函数,返回的对象就是虚拟 dom
会在后续的 patch 过程中进一步转化为 真实 dom。
VDOM 如何做 diff 的?
- 挂载过程结束后,会记录第一次生成的 VDOM-oldVnode
- 当响应式数据发生变化时,将会引起组件重新render,此时就会生成新的VDOM-newVnode
- 使用 oldVnode 与 newVnode 做 diff操作,将更改的部分应到真实 DOM 上,从而转换为最小量的 dom操作,高效更新视图。
组件化
组件化好处:高内聚、可重用、可组合
- 组件化开发能大幅提高应用开发效率、测试性、复用性等;
- 降低更新范围,只重新渲染变化的组件
补充:
- vue中的每个组件都有一个渲染函数watcher、effect
- 数据时响应式的,数据变化后会执行watcher或者effect。
- 组件要合理的划分,如果不拆分组件,那更新的时候整个页面都要重新更新。
- 如果过分的拆分组件会导致watcher、effec产生过多,也会产生性能浪费
对响应式数据的理解
如何实现响应式数据
数组和对象类型当值变化时如何劫持到。对象内部通过defineReactive方法,使用数组则是通过重写数组方法来实object.defineProperty将属性进行劫持(只会劫持已经存在的属性),现。 多层对象是通过递归来实现劫持。vue3则采用 proxy。
vue2处理缺陷
- 在 vue2 的时候使用 defineProperty 来进行数据的劫持,需要对属性进行重写添加getter及setter 性能差。
- 当新增属性和删除属性时无法监控变化。需要通过$set、$delete实现
- 数组不采用 defineProperty 来进行劫持(浪费性能,对所有索引进行劫持会造成性能浪费)需要对数组单独进行处理。
- 对于 ES6 中新产生的 Map、Set 这些数据结构不支持。
vue2中响应式:
1 | let obj = {name:'hello',age:30} |
vue3中响应式
1 | letobj = {name:'hello',age:30,n:[1,2,3,4,5]} |
整理一下vue2使用Obejct.defineProperty()和vue3中使用Proxy代理的区别:
- Proxy可以劫持的数组的改变,defineProperty 需要变异。可以理解为在数组实例和原型之间,插入了一个新的原型的对象,这个原型方法实现了变异的方法,也就真正地拦截了数组原型上的方法
- Proxy代理可以劫持对象的改变,defineProperty需要遍历
- Proxy代理可以劫持对象属性的添加,defineProperty用this.$set来实现
- Proxy可以直接添加属性
vue中如何检测数组的变化
实现数组劫持
- 数组考虑性能原因没有用defineProperty对数组的每一项进行拦截,而是选择重写数组(push,shift,pop,splice,unshift,sort,reverse)方法
- 数组中如果是对象数据类型也会进行递归劫持
数组的缺点
- 数组的索引和长度变化是无法监控到的
vue中如何进行依赖收集
依赖收集流程
- 每个属性都拥有自己的 dep属性,存放他所依赖的 watcher,当属性变化后会通知自己对应的 watcher去更新
- 默认在初始化时会调用 render 函数,此时会触发属性依赖收集 dep.depend
- 当属性发生修改时会触发watcher更新 dep.notify()
vue3中依赖收集
- vue3中会通过Map结构将属性和
effect
映射起来 - 默认在初始化时会调用render函数,此时会触发属性以来手机track
- 当属性发生修改时会找到对应的efect列表一次执行trigger
vue.set方法实现原理
vue不允许在已经创建的实例上动态添加新的响应式属性
1 | export function set(taarget,key,val){ |
当我们选择新增属性时,可以考虑使用对象合并的方式实现
1 | this.info = {...this.info,{newPorperty1:1,newProerty2:2,...}} |
v-show和v-if的优先级
基本概念
- v-if如果条件不成立不会渲染当前指令所在节点的dom元素
- v-show知识切换当前dom的限时或者隐藏
如何选择
- v-if可以阻断内部代码是否执行,如果条件不成立不会执行内部逻辑
- 如果页面逻辑在第一次加载时已经背确认后续不会频繁更改则采用v-if
watch和computed
vue2中有三种watcher(渲染watcher、计算属性watcher、用户watcher)
vue3中有三种effect(渲染effect、计算属性effect、用户effect)
computed
- 计算属性仅当用户取值时才会执行对应的方法
- computed属性时具备缓存的,依赖的值不发生变化,对其取值时计算属性方法不会重新执行
- 计算属性可以简化模板中复杂表达式
- 计算属性中不支持异步逻辑
- computed属性可以在模板中使用
计算属性会创建一个watcher,这个watcher(lazy:true)不会立即执行
通过Object.defineProperty将计算属性定义到实例上
当用户取值时会触发getter,拿到计算属性对应的watcher,看dirty是否为true,如果为true则求值
若依赖的值没有发生变化,则采用缓存
watch
watch时监控值的变化,当值发生变化时调用对应的回调函数。经常用于监控某个值的变化,进行一些操作。(异步要注意竞态问题)
vue3提供了onCleanup函数,让用户更加方便使用也解决了清理问题。
其他
模板表达式,写的太长、不好维护
方法来实现:没有缓存,多次调用
ref和reactive区别
基本概念
ref和reactive时vue3数据响应式中非常重要的两个概念
- reactive用于处理对象类型的数据响应式,底层采用的是new Proxy()
- ref通常用于处理单值的响应式,ref主要解决原始值的响应式问题。底层采用的是Object.defineProperty()实现
reactive中如果存一个ref响应式,那么使用时vue会自动为我们拆包,使用时不必再加上value
watch和watchEffect的区别
- watchEffect 立即运行一个函数,然后被动地追踪它的依赖,当这些依赖改变时重新执行该函数。
- watch 侦测一个或多个响应式数据源并在数据源变化时调用一个回调函数。
1 | // 底层 |
如何将template转换成render函数
Vue 中含有模版编译的功能,它的主要作用是将用户编写的 template 编译为is 中可执行的 render 函数。
- 将 template 模板转换成ast抽象语法树-parserHTML
- 对静态语法做静态标记-markup diff来做优化的 静态节点跳过diff操作
- 重新生成代码-codeGen
new Vue()这个过程中做了什么
- 在new Vue 的时候 内部会进行初始化操作。
- 内部会初始化组件绑定的事件,初始化组件的父子关系(lifecycle)$parent$children $root
- 初始化响应式数据 data、computed、props、watch、method。同时也初始化了provide和inject方法。内部会对数据进行劫持 对象采用 defineProperty数组采用方法重写。
- 在看一下用户是否传入了el属性和template 或者render。render 的优先级更高,如果用户写的是template,会做模板编译(三部曲)。最终就拿到了render函数
- 内部挂载的时候会产生一个 watcher,会调用render 函数会触发依赖收集。内部还会给所有的响应式数据增加 dep 属性,让属性记录当前的 watcher (用户后续修改的时候可以触发 watcher 重新渲染)
- vue 更新的时候采用虚拟 DOM 的方式进行 diff 算法更新。
可结合vue2文档中生命周期图片加深理解
Vue.observable
vue2.6中新增的api,可以将一个普通对象变成响应式对象。
在非父子组件通信时,可以使用eventBus或者使用状态管理工具,但是功能不复杂的时候我们可以考虑使
用vue.observable
在vue3中此api完全可以使用reactive替代,不再使用此api。
v-if和v-for优先级
在vue2中,v-if和v-for在统一标签内使用,v-for优先级会更高。编译结果是优先循环遍历,然后在回调的返回值中用三元判断替代if。
在vue3中,v-if优先级高于v-for,编译后的结果是先判断是否满足条件,满足后再循环执行。
但实际使用时,应当避免v-for和v-if连用。
vue生命周期
vue2中生命周期
主要生命周期有:创建前后、挂载前后、更新前后,销毁前后。
- beforeCreate 初始化父子关系及事件,数据观测(data observer)之前被调用。用此方法一般编写插件的时候会用到。
- created 实例已经创建完成之后被调用。在这一步,实例已完成以下的配置:数据观测(data observer),属性和方法等,但是这里没有$el,一般也不咋用。
- beforeMount 在挂载开始之前被调用:相关的render函数首次被调用。
- mounted el被新创建的 vm.$el 替换,并挂载到实例上去之后调用该钩子。可以用于获取 DOM 元素。
- beforeUpdate 数据更新时调用,发生在虚拟 DOM 重新渲染和打补丁之前。此时修改数据不会再次出发更新方法。
- updated 由于数据更改导致的虚拟 DOM 重新渲染和打补丁,在这之后会调用该钩子。
- beforeDestroy 实例销毁之前调用。在这一步,实例仍然完全可用。
- destroyed Vue 实例销毁后调用。调用后,Vue 实例指示的所有东西都会解绑定,所有的事件监听器会被移除,所有的子实例也会被销毁。该钩子在服务器端渲染期间不被调用。
- keep-alive (activated和deactivated)
vue3中生命周期
声明周期v2 | 生命周期v3 | 描述 |
---|---|---|
beforeCreate | beforeCreate | 组件实例被创建之初 |
created | created | 组件实例已经完全创建 |
beforeMount | beforeMount | 组件挂载之前 |
mounted | mounted | 组件挂载到实例上去之后 |
beforeUpdate | beforeUpdate | 组件数据发生变化,更新之前 |
updated | updated | 数据数据更新之后 |
beforeDestroy | beforeUnmount | 组件实例销毁之前 |
destroyed | unmounted | 组件实例销毁之后 |
activated | activated | keep-alive 缓存的组件激活时 |
deactivated | deactivated | keep-alive 缓存的组件停用时调用 |
errorCaptured | errorCaptured | 捕获一个来自子孙组件的错误时被调用 |
- | renderTracked Dev | 调试钩子,响应式依赖被收集时调用 |
- | renderTriggered Dev | 调试钩子,响应式依赖被触发时调用 |
- | serverPrefetch | ssronly,组件实例在服务器上被渲染前调用 |
Vue3 中新增了,组合式 API:生命周期钩子,但是不存在 onBeforeCreate 和onCreated 钩子
一般在哪一步发送请求
因为请求是异步的,生命周期钩子是同步执行的,所以根据业务安排请求在哪一步,一般在mounted中