文章详情

短信预约-IT技能 免费直播动态提醒

请输入下面的图形验证码

提交验证

短信预约提醒成功

Vue2剥丝抽茧-响应式系统之数组

2024-12-02 04:22

关注

... ...

虽然 list 的值是数组,但我们是对 data.list 进行整体赋值,所以依旧会触发 data.list 的 set ,触发 Watcher 进行重新执行,输出如下:

场景 2

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 还有数组的其他方法也生效,我们需要去重写它们,通过 代理模式,我们可以将数组的原方法先保存起来,然后执行,并且加上自己额外的操作。




import { def } from "./util";

const arrayProto = Array.prototype;
export const arrayMethods = Object.create(arrayProto);

const methodsToPatch = [
"push",
"pop",
"shift",
"unshift",
"splice",
"sort",
"reverse",
];


methodsToPatch.forEach(function (method) {
// cache original method
const original = arrayProto[method];
def(arrayMethods, method, function mutator(...args) {
const result = original.apply(this, args);

// 待补充

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"] 是数组,也就是对象,通过上篇 响应式系统之深度响应 我们知道,它也会去调用 Observer 函数。

那么,我是不是在 Observer 中也加一个 Dep 就可以了。

这样当我们调用数组方法去修改 ['hello'] 的值的时候,去通知 Observer 中的 Dep 就可以了。

收集依赖代码实现

按照上边的思路,完善一下 Observer 类。

export class Observer {
constructor(value) {

this.dep = new Dep();

this.walk(value);
}


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 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);
}
}


walk(obj) {
const keys = Object.keys(obj);
for (let i = 0; i < keys.length; i++) {
defineReactive(obj, keys[i]);
}
}
}

function protoAugment(target, src) {

target.__proto__ = src;

}



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 即可。



import { def } from "./util";

const arrayProto = Array.prototype;
export const arrayMethods = Object.create(arrayProto);

const methodsToPatch = [
"push",
"pop",
"shift",
"unshift",
"splice",
"sort",
"reverse",
];


methodsToPatch.forEach(function (method) {
// cache original method
const original = arrayProto[method];
def(arrayMethods, method, function mutator(...args) {
const result = original.apply(this, args);

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 函数了。

当前的依赖就变成了下边的样子:

总结

对于数组的响应式我们解决了三个问题,依赖放在哪里、收集依赖和通知依赖。

我们来和普通对象属性进行一下对比。

来源:windliang内容投诉

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

软考中级精品资料免费领

  • 历年真题答案解析
  • 备考技巧名师总结
  • 高频考点精准押题
  • 2024年上半年信息系统项目管理师第二批次真题及答案解析(完整版)

    难度     813人已做
    查看
  • 【考后总结】2024年5月26日信息系统项目管理师第2批次考情分析

    难度     354人已做
    查看
  • 【考后总结】2024年5月25日信息系统项目管理师第1批次考情分析

    难度     318人已做
    查看
  • 2024年上半年软考高项第一、二批次真题考点汇总(完整版)

    难度     435人已做
    查看
  • 2024年上半年系统架构设计师考试综合知识真题

    难度     224人已做
    查看

相关文章

发现更多好内容

猜你喜欢

AI推送时光机
位置:首页-资讯-后端开发
咦!没有更多了?去看看其它编程学习网 内容吧
首页课程
资料下载
问答资讯