1、diff算法

传统diff:跨层级,跨层级的组件移动比较少,
react:同层级比较,如果有跨级,直接删掉整个子元素
tag,key:为了复用
vue循环的时候,不建议用下标做key,增删会混乱
sameNode????

1、更新DOM的时候会出现性能问题:向列表里新增一条数据,这条数据以及后面的数据key值都会发生变化(数组的操作特性:删掉元素后,后面的元素会补上来),
vue中是使用key值来进行复用的(判断是否是sameNode),所以vue会认为这条及后面的数据都需要重新渲染
2、会出现bug,比如说check选中,如果选中第三条数据并删除,结果第四条数据自动又被选上了

vue 的就地复用策略:如果数据项的顺序被改变,vue将不会移动dom元素来匹配数据项的顺序,而是简单复用此处每个元素

react :性能问题:同vue的性能问题

2、虚拟DOM

为什么要使用虚拟DOM

  • 比较简单的页面,手工优化的效率比虚拟DOM高,但是在页面结构比较复杂的情况下,手工优化花时间,可维护性不高
  • 是 过多dom操作带来的性能影响 的一种解决方案

一个嵌套的对象,一棵树,里面有对dom节点的各种属性的描述
h,createElement

深度遍历,给每个节点添加索引,便于后续渲染差异

步骤1、数据—>虚拟dom,(可以用正则匹配,可以用babel插件,状态机提取 JSX或者模板中的标签属性等)
2、渲染成dom的调度,如vue的nextTick,挂载
3、响应式,监听数据自动取render

4、diff相关,oldNodeTree,newNodeTree对比,然后调度(避免每次都出发dom更新),然后更新dom

什么是虚拟dom.创建虚拟dom,使用虚拟dom,渲染,diff(patch)

最长上升子序列

3、vue2.x精简版源码

3.1响应式原理

  • Observer :给对象的属性添加getter ,setter,用于依赖收集和派发更新
  • Dep:用于搜集当前响应式的依赖关系,每个响应式对象都有一个Dep实例。Dep.subs = watcher[].当数据发生变更的事后,会通过dep.notify() 通知watcher
  • Watcher: 观察者对象, render watcher computed watcher user watcher
  • 依赖搜集

1、initState : 对cpmputed属性初始化时,会触发 computed watcher 依赖收集
2、initState : 对监听属性初始化的时候,会触发 user watcher 依赖收集
3、render ,触发 render watcher 依赖搜集

  • 派发更新

Object.defineproperty.
1、组件中对响应的数据做了修改,会触发setter逻辑
2、dep.notify()
3、遍历subs (就是存watcher那个数组),执行watcher的 update方法

总结: 创建vue实例时, vue 会遍历data 中所有属性, Object.defineproperty为每个属性添加seeter(派发更新) ,getter(依赖收集),对数据的读取进行劫持
每个组件实例都有对应的 watcher

  1. export class myVue{
  2. constructor(options = {}){
  3. this.$options = options;
  4. this.$el = typeof options.el ==='string'?document.querySelector(options.el):options.el
  5. // else if(options.el instanceof HTMLElement)
  6. if(!this.$el){
  7. throw new Error('传入的el不合法 ')
  8. }
  9. this.$data = options.data
  10. this.$method = options.methods
  11. //代理数据到this上,方便访问
  12. this.proxy(this.$data);
  13. //拦截器 observer,负责把data中的属性转为响应式的数据,new Dep(),并且在get中收集依赖,
  14. //set中notify
  15. new Observer(this.$data)
  16. //负责调用Compiler 解析指令,插值表达式,编译模板,负责页面的首次渲染,
  17. //数据变化后重新渲染视图,为每个节点 添加一个watcher(实例,key,cb),watcher中会先
  18. //保存当前的wacher(添加订阅者),并获取一下值vm[key],触发get,收集依赖(dep.add),
  19. //,值更新的时候,触发了set,然后触发了notify,通知订阅者,调用订阅者watcher的update(cb回调),
  20. new Compiler(this)
  21. }
  22. proxy(data){ //这个方法只是代理 ,使可以直接以 this.xx访问
  23. Object.keys(data).forEach(key=>{
  24. Object.defineProperty(this,key,{
  25. enumerable:true,
  26. configurable:true,
  27. set(newVal){
  28. if(newVal === data[key] || (Number.isNaN(newVal)&&Number.isNaN(data[key]))) return
  29. data[key] = newVal
  30. },
  31. get(){
  32. return data[key]
  33. }
  34. })
  35. })
  36. }
  37. }
  38. //发布订阅
  39. //储存所有的观察者
  40. //每个watcher都有一个update方法
  41. class Dep{ //收集器
  42. constructor(){
  43. this.deps = new Set()
  44. }
  45. add(dep){
  46. if(dep && dep.update) this.deps.add(dep)
  47. }
  48. notify(){
  49. this.deps.forEach(dep=>dep.update())
  50. }
  51. }
  52. class Observer{
  53. constructor(data){
  54. this.walk(data)
  55. }
  56. walk(data){
  57. if(!data || typeof data !=='object')return
  58. Object.keys(data).forEach(key=>{
  59. this.defineReactive(data,key,data[key])
  60. })
  61. }
  62. defineReactive(data,key,value){
  63. const self = this;
  64. this.walk(value); //value可能是一个对象
  65. //数组因为性能问题, ,vue重写了 数组部分方法
  66. let dep = new Dep() //为什么要在这里实例化。因为get ,set的时候需要 用
  67. Object.defineProperty(data,key,{
  68. configurable:true,
  69. enumerable:true,
  70. set(newVal){
  71. if(value === newVal) return
  72. val = newVal
  73. self.walk(newVal) //新值也可能是一个对象
  74. dep.notify()
  75. },
  76. get(){
  77. //watcher实例 //new watcher 的时候会触发这里,搜集依赖
  78. Dep.target && dep.add(Dep.target)
  79. return value
  80. },
  81. })
  82. }
  83. }
  84. class Watcher {
  85. constructor(vm,key,cb){
  86. this.vm = vm //Vue实例
  87. this.key = key //data中的属性名
  88. this.cb = cb //负责更新视图的回调
  89. Dep.target = this;
  90. //这里this指的是watcher实例
  91. //z这里存下来的意义是为了下一步获取旧值,处罚getter的时候拿到 这个watcher
  92. //然后将这个watcher添加到dep依赖
  93. //并且同一时间只维持一个watcher,---数据获取比较频繁的情况下,
  94. //不能每次获取都添加一个watcher进去(同一个属性来说哈)
  95. this._old = vm[key] //存一下老值,触发了getter,add 了 dep
  96. Dep.target = null //释放内存 ,一次性的操作,上一步已经拿到了这个 watcher实例的key,避免重复的添加,
  97. //然后交给下一个watcher
  98. }
  99. update(){
  100. let newValue = this.vm[this.key]
  101. if(newValue === this._old) return
  102. this.cb(newValue)
  103. }
  104. }
  105. class Compiler{
  106. constructor(vm){
  107. this.vm = vm
  108. this.el = vm.$el
  109. this.methods = vm.$methods
  110. this.compile(vm.$el)
  111. }
  112. compile(el){
  113. let childNodes = el.childNodes
  114. Array.from(childNodes).forEach(node=>{
  115. if(this.isTextNode(node)){
  116. this.compileText(node)
  117. }else if(this.isElementNode(node)){
  118. this.compileElement(node)
  119. }
  120. //如果当前 节点中 还有子节点,递归解析
  121. if(node.childNodes && node.childNodes.length) this.compile(node)
  122. })
  123. }
  124. compileElement(node){
  125. if(node.attributes.length){
  126. Array.from(node.attributes).forEach(attr=>{
  127. let attrName = attr.attrName
  128. if(this.isDirective(attrName)){
  129. attrName = attrName.indexOf(':')?attrName.substr(5):attrName.substr(2)
  130. let key = attr.value
  131. this.update(node,key,attrName,this.vm[key])
  132. }
  133. })
  134. }
  135. }
  136. update(node,key,attrName,value){
  137. if(attrName === 'text'){
  138. node.textContent = value;
  139. new Watcher(this.vm,key,val=>node.textContent = val)
  140. }else if(attrName === 'click'){
  141. node.addEventListener(attrName,this.methods[key].bind(this.vm))
  142. new Watcher(this.vm,key,val=>node.textContent = value)
  143. }else if(attrName=== "model"){
  144. node.value = value;
  145. new Watcher(this.vm,key,val=>node.value = val)
  146. node.addEventListener('input',()=>{
  147. this.vm[key] = node.value
  148. })
  149. }
  150. }
  151. compileText(node){
  152. let reg = /\{\{(.+?)\}\}/
  153. let value = node.textContent
  154. if(reg.test(value)){
  155. let key = RegExp.$1.trim()
  156. node.textContent = value.replace(reg,this.vm[key])
  157. }
  158. new Watcher(this.vm,key,val=>node.textContent = val)
  159. }
  160. isDirective(str){
  161. return str.startsWith('v-')
  162. }
  163. isElementNode(node){
  164. return node.nodeType === 1
  165. }
  166. isTextNode(node){
  167. return node.nodeType === 3
  168. }
  169. }

计算属性的原理?

computed watcher 通过dirty属性标记计算属性是否需要重新求值,如果他依赖的响应式数据发生了变化,就把dirty属性设置为true
缓存?
只有它的响应式依赖发生改变的时候才会执行重新求值,反之就是拿的缓存···
实际应用?
非常耗时的操作,比如说遍历超大数组,
项目中的运用,表单编辑的时候 保存状态。比如说根据只读,可编辑等等

todo :回顾一下项目中的实际运用

vue.nextTick()原理

  1. Vue.nextTick(()=>{
  2. })
  3. //或者
  4. await Vue.nextTick()

Vue是异步更新dom的,一旦观察到数据变化,把同一个event loop 中观察数据变化的watcher 推送 进这个队列,在下一次事件循环时,vue会清空异步队列,进行dom的更新
promise.then => MutationObserver => setImmediate => setTimeout
如 : vm.someData = “new value”,不会马上进行dom更新,而是在异步队列被清除时才会更新dom

宏任务 =》 微任务队列 =》 UI render (这个是指浏览器的渲染行为,渲染之前dom已经更新了,所以nextTick能拿到更新的dom) \
宏任务 =》 UI render =》 宏任务(setImmediate => setTimeout)

什么时候用?
要获取更新后的dom元素
数据变化后要执行某个操作。而这个操作依赖因数据改变而改变的dom

  1. <template>
  2. <div v-if="load" ref="test"></div>
  3. </template>
  4. async showLoadDiv(){
  5. this.load = true;
  6. await this.nextTick();
  7. //执行 test 的某一个方法
  8. this.$refs.test.xxx()
  9. }

v-model双向绑定原理

3.2 diff 算法

第一步,h函数把模板编译成虚拟dom
第二步,render这个虚拟do(经过一系列操作,渲染到真实dom)
一系列操作指的是:
分支1:如果没有老节点,代表的是第一次渲染,就直接mount
分支2:如果有老节点,同时也有新节点,就去执行diff,!重点
分支3:如果有老节点,没有新节点,就执行删除操作

vue3.0

编译原理
block tree,靶向更新,提升(变量提升,静态字符串,公共样式) 预先字符串化

dfs :深度优先遍历

波比 js内存空间 ,递归,爆栈,尾递归

fiber调度 serveComponent fizz 流式渲染 利用CPU

CPU分片

vue 路由以及异步组件

单页,多页,webpack 多入口
首屏优化:除了首页,其他页面都通过异步组件方式引入

前后端分离,路由控制

前端路由:hash,history

页面交互不会刷新页面,不同url\路由\路径会渲染不同的内容
hash : #,#及后面的内容不会传给服务器
通过hashChange监听变化
#后面的内容改变,页面不会刷新
hash值的改变,会在浏览器访问历史记录,前进后退
hash改变会触发hashChange
window.addEventListener(‘hashChange’,()=>{})
如何更改hash 标签 location.hash = “#aaa”

history:
服务器能拿到所有的url内容,没有# ,美观
部署需要注意html 的访问,如nginx 所有的指向都需要指向index.html
通过popstate 监听路由变化
window.history.back(),window.history.forward(),window.history.go()
window.history.pushstate({} || null,新页面的title,页面的新地址url) 加入一个历史记录 //location.href
window.history.replaceState() //location.replace
popstate 触发时机?
pushstate,replaceState 不会触发,需要手动刷新
1、后退 。back
2、前进 。forward
3、go()
部署的时候 nginx配置
1、index.html存在服务器本地,比如访问 www.baidu.com/main/ 或者 www.baidu.com

  1. location / {
  2. try_files $uri $uri /home/dist/index.html
  3. }
  4. location /main/ {
  5. try_files $uri $uri /home/dist/index.html
  6. }

2、index.html存在远程地址上,如cdn上:
nginx配置 在这个服务i上:www.baidu.com/main/a
index.html的远程服务器地址:www.baidu-cdn.com/file/index.html

  1. location /main/ {
  2. rewrite ^ /file/index.html break
  3. proxy_pass https://www.baidu-cdn.com
  4. }

需要学习一下nginx的配置

手写hash路由

手写history路由

vue 异步组件 /按需加载

路由守卫(全局 和单个,单个的可以放在 vue文件里)
router.beforeEach(to,from,next) =>{
//追踪日志
}
router.afterEach(to,from) =>{
//追踪日志
}
页面级

beforeRouteEnter()
beforeRouteLeave()
beforeRouteUpdate() :触发的时机???更新子路由的时候

  • 各类路由守卫执行的顺序

    1. 1、【组件】,前一个组件的beforeRouteLeave<br /> 2、【全局】beforeEach()<br />3、【组件】如果有路由参数变化,/test => /test/1,触发beforeRouteUpdate()<br />4、【配置文件】下一个组件的beforeEnter<br />5、【组件】 beforeEnter<br />6、【全局】beforEach

页面切换的动画效果
scrollBehavior : router-link 跳转回去没记住位置

如果直接访问懒加载的页面,会加载 首页的资源

场景:订单列表 中,有待支付,全部,已完成 等各种 状态的tab,切换的时候怎么做scrollBehavior

  • 记住scroolTop位置+ active的tab,返回回来的时候高亮active,并 跳转到scroolTop

keep-alive

概念

  • 一个抽象组件,自身不会渲染一个DOM元素,也不会出现在父组件链中,使用keep-alive包裹动态组件时,会缓存不活动的组件实例,而不是销毁他们
  • 用于保存组件的渲染状态

    场景

  • 后台管理系统中切换tab页面时需要将上一次操作的内容保存下来

  • 用户在列表页选择筛选条过滤出一份数据列表,进入详情后再返回列表页:列表页面可以保留用户的筛选状态。如商品列表页 或者 选中的tab
  • 可以避免组件的反复创建和渲染,有效提升系统性能(需要做优化的场景)

    使用方式

    ```javascript //whiteList 需要缓存的页面/组件 //blackList 不需要缓存的页面/组件 //amount 定义缓存组件的上限,超出上限使用LRU的策略置换缓存数据 //需要配合组件或者页面的name

//LRU策略 :内存管理的一种页面置换算法,对于在内存中 但是又不用的数据块(内存块)叫做LRU,操作系统会根据哪些数据属于LRU而将其 //移出内存而腾出空间来加载另外的数据

  1. <a name="hRQ6X"></a>
  2. #### 原理
  3. - 根据 abstract 来判断当前组件虚拟dom是否渲染成真实dom,keep-alive组件为true表示不渲染成真实dom
  4. - 用一个cache对象来缓存虚拟dom。用一个数组来存缓存的虚拟dom的键集合keys,(key是根据组件ID和tag生成)
  5. - destroyed的时候遍历这个cache对象,调用组件内部的destroyed钩子删除所有的缓存(遍历调用pruneCacheEntry,这个函数内部就是去调的组件内部的destroy钩子函数)
  6. - 监听黑白名单的变动,实时更新cache对象的数据
  7. - render的时候,根据黑白名单匹配决定是否缓存,不缓存的直接返回组件实例(VNode),否则
  8. - 生成缓存key,并在cache中去查找该key,如果存在,取出缓存值添加到vnode.componentInstance上,更新key 在keys中的位置(更新key位置是实现LRU置换的关键),如果不存在
  9. - 将该组件实例存入cache中并且保存key到keys中,然后判断缓存实例的个数是否超出了max,如果超出根据LRU置换策略删除最久未使用的实例(就是下标为0的,这个就是为什么前面要更新key的位置)
  10. - 将该组件实例的keepAlive属性设置为true
  11. - keep-alive包裹的组件时如何使用缓存的?
  12. 在patch阶段,会执行creatComponent函数
  13. ```javascript
  14. function createComponent (vnode, insertedVnodeQueue, parentElm, refElm) {
  15. let i = vnode.data
  16. if (isDef(i)) {
  17. const isReactivated = isDef(vnode.componentInstance) && i.keepAlive
  18. if (isDef(i = i.hook) && isDef(i = i.init)) {
  19. i(vnode, false)
  20. //首次加载被包裹的组件时,keepAlive为true,vnode.componentInstance值是undefined
  21. //它的render函数会先于被包裹组件执行,那么只执行到这里
  22. }
  23. if (isDef(vnode.componentInstance)) {
  24. //再次访问被包裹组件时,vnode.componentInstance是已经缓存组件的实例,就执行insert
  25. //直接把上一次的dom插入到父元素
  26. initComponent(vnode, insertedVnodeQueue)
  27. insert(parentElem, vnode.elem, refElem) // 将缓存的DOM(vnode.elem) 插入父元素中
  28. if (isTrue(isReactivated)) {
  29. reactivateComponent(vnode, insertedVnodeQueue, parentEle, refElm)
  30. }
  31. return true
  32. }
  33. }
  34. }

参考:https://www.jianshu.com/p/9523bb439950