vue原理题

896 阅读5分钟

一、vue响应式数据的原理

1、 对于对象,在2.X版本中使用Object.defineProperty方法,在get方法中,收集依赖watcher;在set方法中触发数据对应的依赖更新。

二、vue如何监测数组中的变化

  • 首先改写了'push','pop','shift','unshift','splice','sort','reverse'七种方法,使得在调用原生方法基础上通知视图更新
  • 在调用'push',‘unshift’,'splice'插入数据时,对插入的数据进行观测
  • 对于数组的每一项调用this.observeArray(value)进行拦截,如果数组项是对象则进行深度数据观测

说明: arr = [{a: 1}, 2] 当修改arr[0].a = 100, 视图是会更新的;若修改arr[1] = 100,则视图不会更新

三、vue为什么采用异步渲染

  • 理解: 如果不采用异步更新,那么每次更新数据就会对当前组件重新渲染。所以为了性能考虑,vue会在本轮数据更新后再去更新视图。
  • 原理:dep.notify() 通知watcher进行更新操作 -> subs[i].update() 依次调用watcher的update -> queuewatcher 将watcher去重放入队列中 -> nextTick() 异步晴空watcher队列

四、nextTick的实现原理

  • 理解:Vue 在更新 DOM 时是异步执行的。只要侦听到数据变化,Vue将开启一个队列,并缓冲在同一事件循环中发生的所有数据变更。

  • 如果同一个 watcher被多次触发,只会被推入到队列中一次。这种在缓冲时去除重复数据对于避免不必要的计算和 DOM 操作是非常重要的。

  • 然后,在下一个的事件循环“tick”中,Vue 刷新队列并执行实际 (已去重的) 工作。 Vue 在内部对异步队列尝试使用原生的Promise.thenMutationObserver(h5原生方法) 和 setImmediate,如果执行环境不支持,则会采用 setTimeout(fn, 0) 代替。

  • 在 vue2.5 的源码中,macrotask降级的方案依次是:setImmediate、MessageChannel、setTimeout

  • vue 的 nextTick 方法的实现原理:

vue 用异步队列的方式来控制 DOM 更新和 nextTick 回调先后执行 microtask 因为其高优先级特性,能确保队列中的微任务在一次事件循环前被执行完毕 考虑兼容问题,vue 做了 microtask 向 macrotask 的降级方案

五、vue中computed的特点

  • 默认computed也是一个watcher,具备缓存属性,只有当依赖的属性发生变化时才会更新视图
  • watch和computed的内部原理是一样的,computed具有缓存属性,并且会立即执行;配置项options:{lazy: true} 表明是计算属性watcher
  • watch,不具备缓存属性,如果immedate为false时,初始化不会立即执行,配置项 options:{user: true} 表明是用户自定义的watcher

六、watch中的deep:true是如何实现的

  • 使用的递归的方法,对对象中的每一项进行求值,会将当前的watcher存入对应的属性依赖中,这样当对象发生变化时也会通知数据更新。所以性能不高

七、v-if 和 v-show 的区别

  • v-if:在编译的时候,如果为true则生成ast树;false时创建空节点
  • v-show:在编译时生成指令,在指令中 el.style.display= value ?originalDisplay: 'none'
  • v-for比v-if的优先级高,所以连用的时候会对 循环的每一项进行判断,这样存在性能问题

八、template先转成ast树,然后ast树 通过codegen 生成render函数,render函数内部通过调用_c即createElement方法生成虚拟dom

九、vue中的diff算法

  • 首先,对于新旧vNode都有两个指针,newStartIndex,newEndIndex,oldStartIndex,oldEndIndex
  • 第一步:头指针指向的节点相同时sameVnode(oldStartVnode, newStartVnode),则指针同时向后移动
  • 第二步:sameVnode(oldEndVnode, newEndVnode)尾指针指向节点相同时,同时向前移动
  • 第三步: sameVnode(oldStartVnode, newEndVnode) oldVNode指针向右移动,新的指针向左移动
  • 第四步:sameVnode(oldEndVnode, newStartVnode)oldVNode指针向左移动,新的指针向右移动
  • 第五步:根据key来查找,newStartIndex从前往后移动,通过key判断当前节点是否存在oldVNode中,如果存在,就将其移动oldStartIndex前一个位置,oldStartIndex还是指向原来的节点,newStartIndex依次向右移动,最后oldVNode删除没有被查找到的节点
  • 如果没有key,vue会重新创建dom,存在key只是移动dom,性能得到提高

十、组件中的data为什么是一个函数

*vue.extend是通过一个对象创建了一个构造函数vueComponent, vue组件都是构造函数的实例,如果data是对象,那么所有实例恶斗共享同一个对象;是一个函数通过闭包,避免组件之间的数据互相影响,保证组件数据的独立性,必须使用一个函数返回一个对象作为组件状态

  • new Vue({data: {}})这里面的data可以是对象,因为不会被共用

十一、v-model

我们在 vue 项目中主要使用 v-model 指令在表单 input、textarea、select 等元素上创建双向数据绑定,我们知道 v-model 本质上不过是语法糖,v-model 在内部为不同的输入元素使用不同的属性并抛出不同的事件:

  • text 和 textarea 元素使用 value 属性和 input 事件;
  • checkbox 和 radio 使用 checked 属性和 change 事件;
  • select 字段将 value 作为 prop 并将 change 作为事件。

十二、作用域插槽和普通插槽的区别

  • 普通插槽渲染到父组件中
  • 作用域插槽渲染到组件中