关注公众号“执鸢者”,回复“材料”获取500G材料(各“兵种”均有),还有业余交换群等你一起来洒脱。(哈哈)
在Vue中,其中最最最外围的一个知识点就是数据响应式原理,数据响应式原理归纳起来就蕴含两大部分:侦测数据变动、依赖收集,理解这两个知识点就理解到了数据响应式原理的精髓。
一、侦测数据变动
可能帧听到数据变动是数据响应式原理的前提,因为数据响应式正是基于监听到数据变动起初触发一系列的更新操作。本次介绍数据响应式原理将基于Vue2.x进行,其将数据变为可被侦测数据时次要采纳了Object.defineProperty()。
1.1 非数组对象
上面先举一个非数组对象的例子
const obj = { a: { m: { n: 5 } }, b: 10};
察看下面的对象,能够发现其是存在蕴含关系的(即一个对象中可能蕴含另一个对象),那么天然会想到通过递归的形式实现,在Vue中为了保障代码较高的可读性,引入了三个模块实现该逻辑:observe、Observer、defineReactive,其调用关系如下所示:
1.1.1 observe
这个函数时帧听数据变动的入口文件,通过调用该函数一方面触发了其帧听对象数据变动的能力;另一方面定义了何时递归到最内层的终止条件。
import Observer from './Observer';export default function (value) { // 如果value不是对象,什么都不做(示意该递归到的是根本类型,其变动可被帧听的) if (typeof value !== 'object') { return; } // Observer实例 let ob; // __ob__是value上的属性,其值就是对应的Observer实例(示意其曾经是可帧听的状态) if (typeof value.__ob__ !== 'undefined') { ob = value.__ob__; } else { // 是对象且该上属性还是未可能帧听状态的 ob = new Observer(value); } return ob;}
1.1.2 Observer
这个函数的目标次要有两个:一个是将该实例挂载到该对象value的__ob__属性上(observe上用到了该属性,通过判断是否有该属性判断是否曾经属于帧听状态);另一个是遍历该对象上的所有属性,而后将该属性均变为可帧听的(通过调用defineReactive实现)。
export default class Observer { constructor(value) { // 给实例增加__ob__属性 def(value, '__ob__', this, false); // 查看是数组还是对象 if (!Array.isArray(value)) { // 若为对象,则进行遍历,将其上的属性变为响应式的 this.walk(value); } } // 对于对象上的属性进行遍历,将其变为响应式的 walk(value) { for (let key in value) { defineReactive(value, key); } }}
1.1.3 defineReactive
这个办法次要是将Object.defineProperty封装到一个函数中,做这一步操作的起因是因为Object.defineProperty设置set属性时须要一个长期变量来存储变动前的值,通过封装利用闭包的思维引入val,这样就不须要在函数里面再设置长期变量了。
export default function defineReactive(data, key, val) { if (arguments.length === 2) { val = data[key]; } // 子元素要进行observe,至此造成了递归 let childOb = observe(val); Object.defineProperty(data, key, { // 可枚举 enumerable: true, // 可配置 configurable: true, // getter get() { console.log(`拜访${key}属性`); return val; }, // setter set(newValue) { console.log(`扭转${key}的属性为${newValue}`); if (val === newValue) { return; } val = newValue; // 当设置了新值,这个新值也要被observe childOb = observe(newValue); } });}
1.2 数组
Object.defineProperty不能间接监听数组外部的变动,那么数组内容变动应该怎么操作呢?Vue次要采纳的是改装数组办法的形式(push、pop、shift、unshift、splice、sort、reverse),在保留其原有性能的前提下,将其新增加的项变为响应式的。
// array.js文件// 失去Array的原型const arrayPrototype = Array.prototype;// 以Array.prototype为原型创立arrayMethods对象,并裸露export const arrayMethods = Object.create(arrayPrototype);// 要被改写的7个数组办法const methodsNeedChange = [ 'push', 'pop', 'shift', 'unshift', 'splice', 'sort', 'reverse'];methodsNeedChange.forEach(methodName => { //备份原来的办法 const original = arrayMethods[methodName]; // 定义新的办法 def(arrayMethods, methodName, function () { // 复原原来的性能 const result = original.apply(this, arguments); // 将类数组对象转换为数组 const args = [...arguments]; // 数组不会是最外层,所以其上曾经增加了Observer实例 const ob = this.__ob__; // push/unshift/splice会插入新项,须要将插入的新项变成observe的 let inserted = []; switch (methodName) { case 'push': case 'unshift': { inserted = args; break; } case 'splice': { inserted = args.slice(2); break; } } // 对于有插入项的,让新项变为响应的 if (inserted.length) { ob.observeArray(inserted); } ob.dep.notify(); return result; }, false);});
除了改装其原有数组办法外,Observer函数中也将减少对数组的解决逻辑。
export default class Observer { constructor(value) { // 给实例增加__ob__属性 def(value, '__ob__', this, false); // 查看是数组还是对象 if (Array.isArray(value)) { // 扭转数组的原型为新改装的内容 Object.setPrototypeOf(value, arrayMethods); // 让这个数组变为observe this.observeArray(value); } else { // 若为对象,则进行遍历,将其上的属性变为响应式的 this.walk(value); } } // 对于对象上的属性进行遍历,将其变为响应式的 walk(value) { for (let key in value) { defineReactive(value, key); } } // 数组的非凡遍历 observeArray(arr) { for (let i = 0, l = arr.length; i < l; i++) { // 逐项进行observe observe(arr[i]); } }}
二、依赖收集
目前对象中所有的属性曾经变成可帧听状态,下一步就进入了依赖收集阶段,其整个流程如下所示:
其实看了这张神图后,因为能力无限还不是很了解,通过本人的拆分,认为能够分成两个步骤去了解。
- getter中(Object.defineProperty中的get属性)进行收集依赖后的状态
- 紧接着就是触发依赖,该过程是在setter中进行,当触发依赖时所存储在Dep中的所有Watcher均会被告诉并执行,告诉其关联的组件更新,例如数据更新的地位是与Dep1所关联的数据,则其上的Watcher1、Watcher2、WatcherN均会被告诉并执行。
说了这么多,其中最外围的内容无外乎Dep类、Watcher类、defineReactive函数中的set和get函数。
2.1 Dep类
Dep类用于治理依赖,蕴含依赖的增加、删除、发送音讯,是一个典型的观察者模式。
export default class Dep { constructor() { console.log('DEP结构器'); // 数组存储本人的订阅者,这是Watcher实例 this.subs = []; } // 增加订阅 addSub(sub) { this.subs.push(sub); } // 增加依赖 depend() { // Dep.target指定的全局的地位 if (Dep.target) { this.addSub(Dep.target); } } // 告诉更新 notify() { const subs = this.subs.slice(); for (let i = 0, l = subs.length; i < l; i++) { subs[i].update(); } }}
2.2 Watcher类
Watcher类的实例就是依赖,在其实例化阶段会作为依赖存储到Dep中,在对应的数据扭转时会更新与该数据相干的Watcher实例,进行对应工作的执行,更新对应组件。
export default class Watcher { constructor(target, expression, callback) { console.log('Watcher结构器'); this.target = target; this.getter = parsePath(expression); this.callback = callback; this.value = this.get(); } update() { this.run(); } get() { // 进入依赖收集阶段,让全局的Dep.target设置为Watcher自身,就进入依赖收集阶段 Dep.target = this; const obj = this.target; let value; try { value = this.getter(obj); } finally { Dep.target = null; } return value; } run() { this.getAndInvoke(this.callback); } getAndInvoke(cb) { const value = this.get(); if (value !== this.value || typeof value === 'object') { const oldValue = this.value; this.value = value; cb.call(this.target, value, oldValue); } }}function parsePath(str) { const segments = str.split('.'); return obj =>{ for (let i = 0; i < segments.length; i++) { if (!obj) { return; } obj = obj[segments[i]]; } return obj; };}
2.3 defineReactive函数中的set和get函数
Object.defineProperty中的getter阶段进行收集依赖,setter阶段触发依赖。
export default function defineReactive(data, key, val) { const dep = new Dep(); if (arguments.length === 2) { val = data[key]; } // 子元素要进行observe,至此造成了递归 let childOb = observe(val); Object.defineProperty(data, key, { // 可枚举 enumerable: true, // 可配置 configurable: true, // getter get() { console.log(`拜访${key}属性`); // 如果当初处于依赖收集阶段 if (Dep.target) { dep.depend(); // 其子元素存在的时候也要进行依赖收集(集体认为次要是针对数组) if (childOb) { childOb.dep.depend(); } } return val; }, // setter set(newValue) { console.log(`扭转${key}的属性为${newValue}`); if (val === newValue) { return; } val = newValue; // 当设置了新值,这个新值也要被observe childOb = observe(newValue); // 公布订阅模式,告诉更新 dep.notify(); } });}
参考文献
本文是笔者看了邵山欢老师的视频后做的一次总结,邵老师讲的真心很好,爆赞。
1.如果感觉这篇文章还不错,来个分享、点赞吧,让更多的人也看到
2.关注公众号执鸢者,支付学习材料(前端“多兵种”材料),定期为你推送原创深度好文