时间:2022-11-20 10:13:48 | 栏目:vue | 点击:次
本文接Vue2响应式系统 、Vue2 响应式系统之分支切换 ,响应式系统之嵌套、响应式系统之深度响应 还没有看过的小伙伴需要看一下。
import { observe } from "./reactive"; import Watcher from "./watcher"; const data = { list: ["hello"], }; observe(data); const updateComponent = () => { for (const item of data.list) { console.log(item); } }; new Watcher(updateComponent); data.list = ["hello", "liang"];
先可以一分钟思考下会输出什么。
虽然 的值是数组,但我们是对 进行整体赋值,所以依旧会触发 的 ,触发 进行重新执行,输出如下:list
data.list
data.list
set
Watcher
import { observe } from "./reactive"; import Watcher from "./watcher"; const data = { list: ["hello"], }; observe(data); const updateComponent = () => { for (const item of data.list) { console.log(item); } }; new Watcher(updateComponent); data.list.push("liang");
先可以一分钟思考下会输出什么。
这次是调用 方法,但我们对 方法什么都没做,因此就不会触发 了。push
push
Watcher
为了让 还有数组的其他方法也生效,我们需要去重写它们,通过push
代理模式 我们可以将数组的原方法先保存起来,然后执行,并且加上自己额外的操作。
/* * not type checking this file because flow doesn't play well with * dynamically accessing methods on Array prototype */ /* export function def(obj, key, val, enumerable) { Object.defineProperty(obj, key, { value: val, enumerable: !!enumerable, writable: true, configurable: true, }); } */ import { def } from "./util"; const arrayProto = Array.prototype; export const arrayMethods = Object.create(arrayProto); const methodsToPatch = [ "push", "pop", "shift", "unshift", "splice", "sort", "reverse", ]; /** * Intercept mutating methods and emit events */ methodsToPatch.forEach(function (method) { // cache original method const original = arrayProto[method]; def(arrayMethods, method, function mutator(...args) { const result = original.apply(this, args); /*****************这里相当于调用了对象 set 需要通知 watcher ************************/ // 待补充 /**************************************************************************** */ return result; }); });
当调用了数组的 或者其他方法,就相当于我们之前重写属性的 ,上边待补充的地方需要做的就是通知 中的 。push
set
dep
Watcher
export function defineReactive(obj, key, val, shallow) { const property = Object.getOwnPropertyDescriptor(obj, key); // 读取用户可能自己定义了的 get、set const getter = property && property.get; const setter = property && property.set; // val 没有传进来话进行手动赋值 if ((!getter || setter) && arguments.length === 2) { val = obj[key]; } const dep = new Dep(); // 持有一个 Dep 对象,用来保存所有依赖于该变量的 Watcher let childOb = !shallow && observe(val); Object.defineProperty(obj, key, { enumerable: true, configurable: true, get: function reactiveGetter() { const value = getter ? getter.call(obj) : val; if (Dep.target) { dep.depend(); } return value; }, set: function reactiveSetter(newVal) { const value = getter ? getter.call(obj) : val; if (setter) { setter.call(obj, newVal); } else { val = newVal; } dep.notify(); }, }); }
如上边的代码,之前的 是通过闭包,每一个属性都有一个各自的 ,负责收集 和通知 。dep
dep
Watcher
Watcher
那么对于数组的话,我们的 放到哪里比较简单呢?dep
回忆一下现在的结构。
const data = { list: ["hello"], }; observe(data); const updateComponent = () => { for (const item of data.list) { console.log(item); } }; new Watcher(updateComponent);
上边的代码执行过后会是下图的结构。
list
属性在闭包中拥有了 属性,通过 ,收集到了包含 函数的 。Dep
new Watcher
updateCompnent
Watcher
同时因为 的 是数组,也就是对象,通过上篇 list
value
["hello"]
响应式系统之深度响应 (opens new window)我们知道,它也会去调用 函数。Observer
那么,我是不是在 中也加一个 就可以了。Observer
Dep
这样当我们调用数组方法去修改 的值的时候,去通知 中的 就可以了。['hello']
Observer
Dep
按照上边的思路,完善一下 类。Observer
export class Observer { constructor(value) { /******新增 *************************/ this.dep = new Dep(); /************************************/ this.walk(value); } /** * 遍历对象所有的属性,调用 defineReactive * 拦截对象属性的 get 和 set 方法 */ walk(obj) { const keys = Object.keys(obj); for (let i = 0; i < keys.length; i++) { defineReactive(obj, keys[i]); } } }
然后在 中,当前 中的 也去收集依赖。get
Oberver
dep
export function defineReactive(obj, key, val, shallow) { const property = Object.getOwnPropertyDescriptor(obj, key); // 读取用户可能自己定义了的 get、set const getter = property && property.get; const setter = property && property.set; // val 没有传进来话进行手动赋值 if ((!getter || setter) && arguments.length === 2) { val = obj[key]; } const dep = new Dep(); // 持有一个 Dep 对象,用来保存所有依赖于该变量的 Watcher let childOb = !shallow && observe(val); Object.defineProperty(obj, key, { enumerable: true, configurable: true, get: function reactiveGetter() { const value = getter ? getter.call(obj) : val; if (Dep.target) { dep.depend(); /******新增 *************************/ if (childOb) { // 当前 value 是数组,去收集依赖 if (Array.isArray(value)) { childOb.dep.depend(); } } /************************************/ } return value; }, set: function reactiveSetter(newVal) { const value = getter ? getter.call(obj) : val; if (setter) { setter.call(obj, newVal); } else { val = newVal; } dep.notify(); }, }); }
我们已经重写了 方法,但直接覆盖全局的 方法肯定是不好的,我们可以在 类中去操作,如果当前 是数组,就去拦截它的 方法。array
arrray
Observer
value
array
这里就回到 的原型链上了,我们可以通过浏览器自带的 ,将当前对象的原型指向我们重写过的方法即可。js
__proto__
考虑兼容性的问题,如果 不存在,我们直接将重写过的方法复制给当前对象即可。__proto__
import { arrayMethods } from './array' // 上边重写的所有数组方法 /* export const hasProto = "__proto__" in {}; */ export class Observer { constructor(value) { this.dep = new Dep(); /******新增 *************************/ if (Array.isArray(value)) { if (hasProto) { protoAugment(value, arrayMethods); } else { copyAugment(value, arrayMethods, arrayKeys); } /************************************/ } else { this.walk(value); } } /** * 遍历对象所有的属性,调用 defineReactive * 拦截对象属性的 get 和 set 方法 */ walk(obj) { const keys = Object.keys(obj); for (let i = 0; i < keys.length; i++) { defineReactive(obj, keys[i]); } } } /** * Augment a target Object or Array by intercepting * the prototype chain using __proto__ */ function protoAugment(target, src) { /* eslint-disable no-proto */ target.__proto__ = src; /* eslint-enable no-proto */ } /** * Augment a target Object or Array by defining * hidden properties. */ /* istanbul ignore next */ function copyAugment(target, src, keys) { for (let i = 0, l = keys.length; i < l; i++) { const key = keys[i]; def(target, key, src[key]); } }
还需要考虑一点,数组方法中我们只能拿到 值,那么怎么拿到 对应的 呢。value
value
Observer
我们只需要在 类中,增加一个属性来指向自身即可。Observe
export class Observer { constructor(value) { this.dep = new Dep(); /******新增 *************************/ def(value, '__ob__', this) /************************************/ if (Array.isArray(value)) { if (hasProto) { protoAugment(value, arrayMethods); } else { copyAugment(value, arrayMethods, arrayKeys); } } else { this.walk(value); } } ... }
回到最开始重写的 方法中,只需要从 中拿到 去通知 即可。array
__ob__
Dep
Watcher
/* * not type checking this file because flow doesn't play well with * dynamically accessing methods on Array prototype */ import { def } from "./util"; const arrayProto = Array.prototype; export const arrayMethods = Object.create(arrayProto); const methodsToPatch = [ "push", "pop", "shift", "unshift", "splice", "sort", "reverse", ]; /** * Intercept mutating methods and emit events */ methodsToPatch.forEach(function (method) { // cache original method const original = arrayProto[method]; def(arrayMethods, method, function mutator(...args) { const result = original.apply(this, args); /*****************这里相当于调用了对象 set 需要通知 watcher ************************/ const ob = this.__ob__; // notify change ob.dep.notify(); /**************************************************************************** */ return result; }); });
import { observe } from "./reactive"; import Watcher from "./watcher"; const data = { list: ["hello"], }; observe(data); const updateComponent = () => { for (const item of data.list) { console.log(item); } }; new Watcher(updateComponent); data.list.push("liang");
这样当调用 方法的时候,就会触发相应的 来执行 函数了。push
Watcher
updateComponent
当前的依赖就变成了下边的样子:
对于数组的响应式我们解决了三个问题,依赖放在哪里、收集依赖和通知依赖。
我们来和普通对象属性进行一下对比。