Студенты, читавшие официальные документы vue, должны быть хорошо знакомы с этой картиной.
Как реализована отзывчивость vue?
Слышал слишком много ответов, черезObject.defineProperty
, но когда его спросили подробно, другая сторона была совершенно не в курсе.
Уважайте в первую очередь
const Observer = function(data) {
// 循环修改为每个属性添加get set
for (let key in data) {
defineReactive(data, key);
}
}
const defineReactive = function(obj, key) {
// 局部变量dep,用于get set内部调用
const dep = new Dep();
// 获取当前值
let val = obj[key];
Object.defineProperty(obj, key, {
// 设置当前描述属性为可被循环
enumerable: true,
// 设置当前描述属性可被修改
configurable: true,
get() {
console.log('in get');
// 调用依赖收集器中的addSub,用于收集当前属性与Watcher中的依赖关系
dep.depend();
return val;
},
set(newVal) {
if (newVal === val) {
return;
}
val = newVal;
// 当值发生变更时,通知依赖收集器,更新每个需要更新的Watcher,
// 这里每个需要更新通过什么断定?dep.subs
dep.notify();
}
});
}
const observe = function(data) {
return new Observer(data);
}
const Vue = function(options) {
const self = this;
// 将data赋值给this._data,源码这部分用的Proxy所以我们用最简单的方式临时实现
if (options && typeof options.data === 'function') {
this._data = options.data.apply(this);
}
// 挂载函数
this.mount = function() {
new Watcher(self, self.render);
}
// 渲染函数
this.render = function() {
with(self) {
_data.text;
}
}
// 监听this._data
observe(this._data);
}
const Watcher = function(vm, fn) {
const self = this;
this.vm = vm;
// 将当前Dep.target指向自己
Dep.target = this;
// 向Dep方法添加当前Wathcer
this.addDep = function(dep) {
dep.addSub(self);
}
// 更新方法,用于触发vm._render
this.update = function() {
console.log('in watcher update');
fn();
}
// 这里会首次调用vm._render,从而触发text的get
// 从而将当前的Wathcer与Dep关联起来
this.value = fn();
// 这里清空了Dep.target,为了防止notify触发时,不停的绑定Watcher与Dep,
// 造成代码死循环
Dep.target = null;
}
const Dep = function() {
const self = this;
// 收集目标
this.target = null;
// 存储收集器中需要通知的Watcher
this.subs = [];
// 当有目标时,绑定Dep与Wathcer的关系
this.depend = function() {
if (Dep.target) {
// 这里其实可以直接写self.addSub(Dep.target),
// 没有这么写因为想还原源码的过程。
Dep.target.addDep(self);
}
}
// 为当前收集器添加Watcher
this.addSub = function(watcher) {
self.subs.push(watcher);
}
// 通知收集器中所的所有Wathcer,调用其update方法
this.notify = function() {
for (let i = 0; i < self.subs.length; i += 1) {
self.subs[i].update();
}
}
}
const vue = new Vue({
data() {
return {
text: 'hello world'
};
}
})
vue.mount(); // in get
vue._data.text = '123'; // in watcher update /n in get
Здесь мы реализуем простую отзывчивость vue с менее чем 100 строками кода. Конечно, если здесь не рассматривать процесс периода, я считаю, что это можно сделать в рамках 40 строк кода. Но я не хочу опускать это здесь, почему? Я боюсь, что вы автоматически проигнорируете процесс, и что, когда кто-то вас о чем-то спросит, вы это видели, но у вас не будет дара речи. Короче говоря, я пью больше горячей воды ради вас же.
Какова роль Депа?
Сборщик зависимостей, это не официальное название clam, я сделал его сам, просто на память.
Давайте рассмотрим роль сборщика зависимостей на двух примерах.
-
Пример 1, бессмысленный рендеринг не нужен?
const vm = new Vue({ data() { return { text: 'hello world', text2: 'hey', } } })
когда
vm.text2
Когда значение изменится, он будет вызван сноваrender
,а такжеtemplate
в, но не используетсяtext2
, так что разберитесь с этимrender
Это бессмысленно?Для этого примера вы помните, что мы смоделировали выше?
Vue
изrender
В функции мы вызываем значение, связанное с этим рендерингом, поэтому значение, не связанное с рендерингом, не сработает.get
, он не будет добавлен в слушатель в сборщике зависимостей (addSub
метод не сработает), даже если вызовset
назначать,notify
серединаsubs
тоже пусто. Хорошо, продолжайте возвращаться к демоверсии и приступайте к небольшой волне тестов, чтобы подтвердить то, что я сказал.const vue = new Vue({ data() { return { text: 'hello world', text2: 'hey' }; } }) vue.mount(); // in get vue._data.text = '456'; // in watcher update /n in get vue._data.text2 = '123'; // nothing
-
Пример 2, когда несколько экземпляров Vue ссылаются на одни и те же данные, кто уведомляется? Разве оба не должны быть уведомлены?
let commonData = { text: 'hello world' }; const vm1 = new Vue({ data() { return commonData; } }) const vm2 = new Vue({ data() { return commonData; } }) vm1.mount(); // in get vm2.mount(); // in get commonData.text = 'hey' // 输出了两次 in watcher update /n in get
Я надеюсь, что благодаря этим двум примерам вы, вероятно, понялиDep
Вам кажется, что это так? Вот так. Подводя итог (следующий сборщик зависимостей на самом делеDep
):
-
vue
Будуdata
инициализирован вObserver
и для каждого значения в объекте переопределитеget
,set
,data
каждый изkey
, у всех есть отдельный сборщик зависимостей. - существует
get
, добавил прослушиватель в сборщик зависимостей - При монтировании экземпляр
Watcher
, наводя цель коллектора на токWatcher
- существует
data
Срабатывает при изменении значенияset
, который инициирует обновление всех прослушивателей в сборщике зависимостей для запускаWatcher.update
Если вы все еще не чувствуете себя удовлетворенным после прочтения, вы можете прочитать другие статьи автора.