Vue2原理
狐七 3/16/2022 vue
# 1. 请简述 Vue 响应式原理
查看简单答案
- Vue.js 是采用数据劫持结合发布者 - 订阅者模式的方式。数据初始化的时候数据data创建响应式对象,遍历所有的属性用Object.defineProperty方法将其转换成setter和getter。
- 在get方法中获取值并且去收集依赖,如果有子对象就给子对象收集依赖。
- 在set方法中,判断如果设置的新值与旧值不相等,将新值赋值给旧值,然后调用dep.notify方法派发通知更新视图。
查看详细答案
Vue响应式指的是Vue数据响应式,Vue数据响应式指的是数据驱动视图,在数据发生变化的时候自动更新视图,不需要手动操作DOM。
源码中在数据初始化的时候initData中,是通过observe函数给数据data创建响应式对象的,这个函数的功能是通过创建一个Observer构造函数,将数据data的所有属性转化成getter和setter。
主要分为以下几个步骤:
- 需要 observe 的数据对象进行递归遍历,包括子属性对象的属性,都加上 setter 和 getter 这样的话,给这个对象的某个值赋值,就会触发 setter,那么就能监听到了数据变化。
- compile 解析模板指令,将模板中的变量替换成数据,然后初始化渲染页面视图,并将每个指令对应的节点绑定更新函数,添加监听数据的订阅者,一旦数据有变动,收到通知,更新视图。
- Watcher 订阅者是 Observer 和 Compile 之间通信的桥梁,主要做的事情是:
- 在自身实例化时往属性订阅器 (dep) 里面添加自己
- 自身必须有一个 update()方法
- 待属性变动 dep.notice()通知时,能调用自身的 update()方法,并触发 Compile 中绑定的回调,则功成身退。
MVVM 作为数据绑定的入口,整合 Observer、Compile 和 Watcher 三者,通过 Observer 来监听自己的 model 数据变化,通过 Compile 来解析编译模板指令,最终利用 Watcher 搭起 Observer 和 Compile 之间的通信桥梁,达到数据变化 -> 视图更新;视图交互变化 (input) -> 数据 model 变更的双向绑定效果。
# 2. 具体如何收集依赖?
查看答案
- 初始化的时候在$mount方法中调用了mountComponent方法
- mountComponent方法内部创建了Watcher对象
- 在Watcher对象中首先将自己存储到了Dep对象的target属性中,然后调用了updateComponent方法
- updateComponent这个方法将render函数渲染到页面上
- 渲染过程中在访问每个属性的时候,就会进入属性的get方法
- 在get方法中进行依赖收集,将Watcher对象添加到Dep的subs数组中,并且将dep对象添加到Watcher对象的newDeps数组中。
# 3. 具体如何发布通知?
查看答案
- 当值进行修改的时候,会触发属性的set方法,这个时候会调用dep.notify()方法。
- Dep对象会遍历其subs数组,每个元素都是一个Watcher对象,并调用其update方法。
- 然后其会调用updateComponent方法更新视图。
# 4. Object.defineProperty的问题
查看答案
- 劫持的是对象的属性,不管嵌套多深都要一次性遍历到,性能太低
- 数组下标修改数据没有办法进行劫持,大部分方法都拦截不到,只是Vue内部通过重写函数的方式解决了这个问题。
- 给对象直接添加属性没有办法进行劫持,必须要用Vue.set方法
# 5. 观察者模式和订阅发布模式的区别
查看答案
- 观察者模式中主体和观察者是互相感知的。
- 发布订阅模式是借助第三方来实现调度的,发布者和订阅者之间互不感知。
# 6. Vue中key的作用 TODO
查看答案
key 是给每一个 vnode 的唯一id,可以依靠 key 更准确、更快的拿到 oldVnode 中对应的 vnode 节点。
# 7. Virtual DOM 真的比操作原生 DOM 快吗?
查看答案
不一定。这是一个性能 vs. 可维护性的取舍。
框架的意义在于为你掩盖底层的 DOM 操作,让你用更声明式的方式来描述你的目的,从而让你的代码更容易维护。
针对任何一个 benchmark,我都可以写出比任何框架更快的手动优化,框架给你的保证是,你在不需要手动优化的情况下,我依然可以给你提供过得去的性能。
# 8. React 和 Vue 的 diff 时间复杂度从 O(n^3) 优化 到 O(n) ,那么 O(n^3) 和 O(n) 是如何计算出来的?
查看答案
三种优化来降低复杂度:
- 如果父节点不同,放弃对子节点的比较,直接删除旧节点然后添加新的 节点重新渲染。
- 如果子节点有变化,Virtual DOM 不会计算变化的是什么,而是重新渲染。
- 通过唯一的 key 策略。