这道题目是面试中至关高频的一道题目了,但凡你简历上有写:“熟练使用Vue
并阅读过其部分源码”,那么这道题目十有八九面试官都会去问你。前端
什么?你简历上不写阅读过源码,那面试官也颇有可能会问你是否阅读过响应式相关的源码
仍是那句歌词唱的:vue
挣不脱 逃不过 眉头解不开的结 命中解不开的劫
做为一个前端的MVVM
框架,Vue
的基本思路和Angular
、React
并没有二致,其核心就在于: 当数据变化时,自动去刷新页面DOM
,这使得咱们能从繁琐的DOM
操做中解放出来,从而专心地去处理业务逻辑。面试
这就是Vue
的数据双向绑定(又称响应式原理)。数据双向绑定是Vue
最独特的特性之一。此处咱们用官方的一张流程图来简要地说明一下Vue
响应式系统的整个流程:数组
在Vue
中,每一个组件实例都有相应的watcher
实例对象,它会在组件渲染的过程当中把属性记录为依赖,以后当依赖项的setter
被调用时,会通知watcher
从新计算,从而导致它关联的组件得以更新。框架
这是一个典型的观察者模式。
在 Vue 数据双向绑定的实现逻辑里,有这样三个关键角色:函数
Observer
: 它的做用是给对象的属性添加getter
和setter
,用于依赖收集和派发更新Dep
: 用于收集当前响应式对象的依赖关系,每一个响应式对象包括子对象都拥有一个Dep
实例(里面subs
是Watcher
实例数组),当数据有变动时,会经过dep.notify()
通知各个watcher
。Watcher
: 观察者对象 , 实例分为渲染 watcher (render watcher)
,计算属性 watcher (computed watcher)
,侦听器 watcher(user watcher)
三种为何要单独拎出来一小节专门来讲这个问题呢?由于大部分同窗只是知道:Vue
的响应式原理是经过Object.defineProperty
实现的。被Object.defineProperty
绑定过的对象,会变成「响应式」化。也就是改变这个对象的时候会触发get
和set
事件。this
可是对于里面具体的对象依赖关系并非很清楚,这样也就给了面试官一种:你只是背了答案,对于响应式的内部实现细节,你并非很清楚的印象。spa
关于Watcher 和 Dep 的关系
这个问题,其实刚开始我也不是很清楚,在查阅了相关资料后,才逐渐对里面的具体实现有了清晰的理解。设计
刚接触Dep
这个词的同窗都会比较懵: Dep
到底是用来作什么的呢?咱们经过defineReactive
方法将data
中的数据进行响应式后,虽然能够监听到数据的变化了,那咱们怎么处理通知视图就更新呢?双向绑定
Dep
就是帮咱们依赖管理
的。
如上图所示:一个属性可能有多个依赖,每一个响应式数据都有一个Dep
来管理它的依赖。
上面说了那么多,下面我总结一下Vue响应式
的核心设计思路:
当建立Vue
实例时,vue
会遍历data
选项的属性,利用Object.defineProperty
为属性添加getter
和setter
对数据的读取进行劫持(getter
用来依赖收集,setter
用来派发更新),而且在内部追踪依赖,在属性被访问和修改时通知变化。
每一个组件实例会有相应的watcher
实例,会在组件渲染的过程当中记录依赖的全部数据属性(进行依赖收集,还有computed watcher
,user watcher
实例),以后依赖项被改动时,setter
方法会通知依赖与此data
的watcher
实例从新计算(派发更新),从而使它关联的组件从新渲染。
到这里,咱们已经了解了“套路”,下面让咱们用伪代码来实现一下Vue
的响应式吧!
/** * @name Vue数据双向绑定(响应式系统)的实现原理 */ // observe方法遍历并包装对象属性 function observe(target) { // 若target是一个对象,则遍历它 if (target && typeof target === "Object") { Object.keys(target).forEach((key) => { // defineReactive方法会给目标属性装上“监听器” defineReactive(target, key, target[key]); }); } } // 定义defineReactive方法 function defineReactive(target, key, val) { const dep = new Dep(); // 属性值也多是object类型,这种状况下须要调用observe进行递归遍历 observe(val); // 为当前属性安装监听器 Object.defineProperty(target, key, { // 可枚举 enumerable: true, // 不可配置 configurable: false, get: function () { return val; }, // 监听器函数 set: function (value) { dep.notify(); }, }); } class Dep { constructor() { this.subs = []; } addSub(sub) { this.subs.push(sub); } notify() { this.subs.forEach((sub) => { sub.update(); }); } }