是 缩写,也就是把 中的 演变成 。 层代表数据模型,View 代表 UI 组件,
l 是 和 Model 层的桥梁,数据会绑定到 层并自动将数据渲染到页面中,视图变化的时候会通知 层更新数据。
在初始化数据时,会使用 重新定义 中的所有属性,当页面使用对应属性时,首先会进行依赖收集(收集当前组件的 watcher)
主要做了这么几件事:数据劫持、收集依赖、派发更新
1、数据劫持:的时候遍历data对象,用给所有属性加上了。
2、依赖的收集:的过程,会触发数据的getter,在getter的时候把当前的watcher对象收集起来。
3、派发更新:的时候,遍历这个数据的依赖对象(watcher对象),进行更新。
Vue3.x 改用 Proxy 替代 Object.defineProperty。因为 Proxy 可以直接监听对象和数组的变化,并且有多达 13 种拦截方法。
并且作为新标准将受到浏览器厂商重点持续的性能优化。
断当前 Reflect.get 的返回值是否为 Object,如果是则再通过 reactive 方法做代理, 这样就实现了深度观测。
我们可以判断 key 是否为当前被代理对象 target 自身属性,也可以判断旧值与新值是否相等,只有满足以上两个条件之一时,才有可能执行 trigger。
使用了函数劫持的方式,重写了数组的方法,Vue 将 data 中的数组进行了原型链重写,指向了自己定义的数组原型方法。
这样当调用数组 api 时,可以通知依赖更新。如果数组中包含着引用类型,会对数组中的引用类型再次递归遍历进行监控。这样就实现了监测数组变化。
在下次 DOM 更新循环结束之后执行延迟回调。nextTick 主要使用了宏任务和微任务。根据执行环境分别尝试采用
如果以上都不行则采用
定义了一个异步方法,多次调用 nextTick 会将方法存入队列中,通过这个异步方法清空当前队列。
关于宏任务和微任务以及事件循环可以看这篇文章
是 之后触发的第一个钩子,在当前阶段 上的数据和方法都不能被访问。
在实例创建完成后发生,当前阶段已经完成了数据观测,也就是可以使用数据,更改数据,在这里更改数据不会触发 。
可以做一些初始数据的获取,在当前阶段无法与 进行交互,如果非要想,可以通 来访问 Dom。
发生在挂载之前,在这之前 模板已导入渲染函数编译。而当前阶段虚拟 Dom 已经创建完成,即将开始渲染。
在此时也可以对数据进行更改,不会触发 updated。
在挂载完成后发生,在当前阶段,真实的 Dom 挂载完毕,数据完成双向绑定,可以访问到 Dom 节点,使用对 Dom 进行操作。
发生在更新之前,也就是响应式数据发生更新,虚拟 dom 重新渲染之前被触发,你可以在当前阶段进行更改数据,不会造成重渲染。
发生在更新完成之后,当前阶段组件 Dom 已完成更新。要注意的是避免在此期间更改数据,因为这可能会导致无限循环的更新。
发生在实例销毁之前,在当前阶段实例完全可以被使用,我们可以在这时进行善后收尾工作,比如清除计时器。
发生在实例销毁之后,这个时候只剩下了 dom 空壳。组件已被拆解,数据绑定被卸除,监听被移出,子实例也统统被销毁。
-
接口请求一般放在 mounted 中,但需要注意的是服务端渲染时不支持 mounted,需要放到 created 中。
-
created阶段也可以做一些简单的请求(不操作DOM)
-
注意:如果是服务端渲染,需要放到created中。因为只支持beforeCreat与created两个钩子。
Computed 本质是一个具备缓存的 watcher,依赖的属性发生变化就会更新视图。适用于计算比较消耗性能的计算场景。
当表达式过于复杂时,在模板中放入过多逻辑会让模板难以维护,可以将复杂的逻辑放入计算属性中处理。
Watch 没有缓存性,更多的是观察的作用,可以监听某些数据执行回调。当我们需要深度监听对象中的属性时,可以打开 deep:true 选项,
这样便会对对象中的每一项进行监听。这样会带来性能问题,优化的话可以使用字符串形式监听,如果没有写到组件中,不要忘记使用 unWatch 手动注销哦。
-
手段:是动态的向DOM树内添加或者删除DOM元素;是通过设置DOM元素的display样式属性控制显隐
-
编译过程:切换有一个局部编译/卸载的过程,切换过程中合适地销毁和重建内部的事件监听和子组件;只是简单的基于css切换
-
编译条件:是惰性的,如果初始条件为假,则什么也不做;只有在条件第一次变为真时才开始局部编译(编译被缓存?编译被缓存后,然后再切换的时候进行局部卸载); v-show是在任何条件下(首次条件是否为真)都被编译,然后被缓存,而且DOM元素保留
-
性能消耗:有更高的切换消耗;v-show有更高的初始渲染消耗
-
使用场景:适合运营条件不大可能改变;v-show适合频繁切换
-
相同点 都可以动态控制着dom元素的显示隐藏
-
不同点: 的显示隐藏是将DOM元素整个添加或删除,v-show 的显示隐藏是为DOM元素添加css的样式,设置none或者是block,DOM元素是还存在的
-
在渲染多个元素的时候,可以把一个 元素作为包装元素,并使用 进行条件判断,最终的渲染不会包含这个元素,是不支持 语法
一个组件被复用多次的话,也就会创建多个实例。本质上,这些实例用的都是同一个构造函数。如果 是对象的话,对象属于引用类型,会影响到所有的实例。
所以为了保证组件不同的实例之间 data 不冲突, 必须是一个函数。
本质就是一个语法糖,可以看成是 方法的语法糖。可以通过 的 来进行自定义。
原生的 ,会根据标签的不同生成不同的事件和属性
原生事件绑定是通过 绑定给真实元素的,组件事件绑定是通过 Vue 自定义的 实现的。
简单说,Vue 的编译过程就是将 template 转化为 render 函数的过程。会经历以下阶段:
生成 AST 树
优化
codegen
首先解析模版,生成 AST 语法树(一种用 Javascript 对象的形式来描述整个模板)。使用大量的正则表达式对模板进行解析,
遇到标签、文本的时候都会执行对应的钩子进行相关处理。
Vue 的数据是响应式的,但其实模板中并不是所有的数据都是响应式的。有一些数据首次渲染后就不会再变化,对应的 DOM 也不会变化。
那么优化过程就是深度遍历 AST 树,按照相关条件对树节点进行标记。这些被标记的节点(静态节点)我们就可以跳过对它们的比对,对运行时的模板起到很大的优化作用。
编译的最后一步是将优化后的 AST 树转换为可执行的代码。
简单来说, 算法有以下过程
同级比较,再比较子节点
先判断一方有子节点一方没有子节点的情况(如果新的 children 没有子节点,将旧的子节点移除)
比较都有子节点的情况
递归比较子节点
正常 Diff 两个树的时间复杂度是 ),但实际情况下我们很少会进行跨层级的移动 DOM,所以 将 进行了优化,从
只有当新旧 都为多个子节点时才需要用核心的 Diff 算法进行同层级比较。
Vue2 的核心 Diff 算法采用了双端比较的算法,同时从新旧 的两端开始进行比较,借助 key 值找到可复用的节点,再进行相关操作。
相比 React 的 Diff 算法,同样情况下可以减少移动节点次数,减少不必要的性能损耗,更加的优雅。
Vue3.x 借鉴了 ivi 算法和 inferno 算法
在创建 VNode 时就确定其类型,以及在 的过程中采用位运算来判断一个 的类型,在这个基础之上再配合核心的 Diff 算法,
使得性能上较 有了提升。(实际的实现可以结合 Vue3.x 源码看。)
该算法中还运用了动态规划的思想求解最长递归子序列。
由于在浏览器中操作 DOM 是很昂贵的。频繁的操作 DOM,会产生一定的性能问题。这就是虚拟 Dom 的产生原因。
的 借鉴了开源库 的实现。
本质就是用一个原生的 JS 对象去描述一个 DOM 节点。是对真实 DOM 的一层抽象。(也就是源码中的 VNode 类,它定义在 中。)
映射到真实 DOM 要经历 等阶段。
「key 的作用是尽可能的复用 DOM 元素。」
新旧 中的节点只有顺序是不同的时候,最佳的操作应该是通过移动元素的位置来达到更新的目的。
需要在新旧 的节点中保存映射关系,以便能够在旧 的节点中找到可复用的节点。 也就是 中节点的唯一标识。
可以实现组件缓存,当组件切换时不会对当前组件进行卸载。
常用的两个属性 i,允许组件有条件的进行缓存。
两个生命周期 ,用来得知当前组件是否处于活跃状态。
k 的中还运用了 。
组件的调用顺序都是先父后子,渲染完成的顺序是先子后父。
组件的销毁操作是先父后子,销毁完成的顺序是先子后父。
加载渲染过程
子组件更新过程:
父组件更新过程:
销毁过程:
- 父子组件通信
- 兄弟组件通信
- 跨级组件通信
SSR 也就是服务端渲染,也就是将 Vue 在客户端把标签渲染成 HTML 的工作放在服务端完成,然后再把 html 直接返回给客户端。
SSR 有着更好的 SEO、并且首屏加载速度更快等优点。不过它也有一些缺点,比如我们的开发条件会受到限制。
服务器端渲染只支持 beforeCreate 和 created 两个钩子,当我们需要一些外部扩展库时需要特殊处理。
服务端渲染应用程序也需要处于 Node.js 的运行环境。还有就是服务器会有更大的负载需求。
编码阶段
尽量减少 data 中的数据,data 中的数据都会增加 getter 和 setter,会收集对应的 watcher
v-if 和 v-for 不能连用
如果需要使用 v-for 给每项元素绑定事件时使用事件代理
SPA 页面采用 keep-alive 缓存组件
在更多的情况下,使用 v-if 替代 v-show
key 保证唯一
使用路由懒加载、异步组件
防抖、节流
第三方模块按需导入
长列表滚动到可视区域动态加载
图片懒加载
SEO 优化
预渲染
服务端渲染 SSR
打包优化
压缩代码
Tree Shaking/Scope Hoisting
使用 cdn 加载第三方模块
多线程打包 happypack
splitChunks 抽离公共文件
sourceMap 优化
用户体验
骨架屏
PWA
还可以使用缓存(客户端缓存、服务端缓存)优化、服务端开启 gzip 压缩等。
hash模式
原理:
- 在 url 中的 # 之后对应的是 hash 值, 其原理是通过hashChange() 事件监听hash值的变化, 根据路由表对应的hash值来判断加载对应的路由加载对应的组件。
优点:
-
只需要前端配置路由表, 不需要后端的参与。
-
兼容性好, 浏览器都能支持。
-
hash值改变不会向后端发送请求, 完全属于前端路由。
缺点:
- hash值前面需要加#, 不符合url规范,也不美观。
history模式:
原理:
-
利用History_API,进行路由切换, 在mdn上提到了history栈。
-
说明它的存储形势是个栈结构。
优点:
- 符合url地址规范, 不需要#, 使用起来比较美观。
缺点:
-
在用户手动输入地址或刷新页面时会发起url请求, 后端需要配置index.html页面用户匹配不到静态资源的情况, 否则会出现404错误。
-
兼容性比较差, 是利用了 HTML5 History对象中新增的 pushState() 和 replaceState() 方法,需要特定浏览器的支持。
总结:
- 一般情况下两种路由的模式都可以使用, 具体的使用场景可以结合以上两种模式的优缺点进行选择。
🌟让天下没有学不会的技术🌟
学习C#不再是难问题
🌳《C#入门到高级教程》🌳
有关C#实战项目
👉C#RS232C通讯源码👈
👉C#委托数据传输👈
👉C# Modbus TCP 源代码👈
👉C# 仓库管理系统源码👈
👉C# 欧姆龙通讯Demo👈
👉C# 欧姆龙通讯Demo👈
👉2021C#与Halcon视觉通用的框架👈
✨有关C#项目欢迎各位查看个人主页✨
🌟机器视觉、深度学习🌟
🌌《Halcon入门到精通》🌌
🌌《深度学习资料与教程》🌌
有关机器视觉、深度学习实战
👉2021年C#+HALCON视觉软件👈
👉2021年C#+HALCON实现模板匹配👈
👉C#集成Halcon的深度学习软件👈
👉C#集成Halcon的深度学习软件,带[MNIST例子]数据集👈
👉C#支持等比例缩放拖动的halcon WPF开源窗体控件👈
👉2021年Labview联合HALCON👈
👉2021年Labview联合Visionpro👈
👉基于Halcon及VS的动车组制动闸片厚度自动识别模块👈
✨有关机器视觉、深度学习实战欢迎各位查看个人主页✨
🌟Java、数据库教程与项目🌟
🍏《JAVA入门到高级教程》🍏
🍏《数据库入门到高级教程》🍏
有关Java、数据库项目实战
👉Java经典怀旧小霸王网页游戏机源码增强版👈
👉Java物业管理系统+小程序源码👈
👉JAVA酒店客房预定管理系统的设计与实现SQLserver👈
👉JAVA图书管理系统的研究与开发MYSQL👈
✨有关Java、数据库教程与项目实战欢迎各位查看个人主页✨
🌟分享Python知识讲解、分享🌟
🥝《Python知识、项目专栏》🥝
🥝《Python 检测抖音关注账号是否封号程》🥝
🥝《手把手教你Python+Qt5安装与使用》🥝
🥝《用一万字给小白全面讲解python编程基础问答》🥝
🥝《Python 绘制Android CPU和内存增长曲线》🥝
有关Python项目实战
👉Python基于Django图书管理系统👈
👉Python管理系统👈
👉2021年9个常用的python爬虫源码👈
👉python二维码生成器👈
✨有关Python教程与项目实战欢迎各位查看个人主页✨
🌟分享各大公司面试题、面试流程🌟
🍏《2021年金九银十最新的VUE面试题☀️《❤️记得收藏❤️》》🍏
🍏《只要你认真看完一万字☀️Linux操作系统基础知识☀️分分钟钟都吊打面试官《❤️记得收藏❤️》》🍏
🍏《❤️用一万字给小白全面讲解python编程基础问答❤️《😀记得收藏不然看着看着就不见了😀》》🍏
✨有关各大公司面试题、面试流程欢迎各位查看个人主页✨