Я написал один раньшеПринцип отзывчивости — как следить за изменениями в массиве, Недавно я собирался поделиться им с коллегами по команде, но раньше он был слишком грубым, поэтому я решил написать подробную версию~
1. Как следить за изменением индекса массива?
(1) Анализ случая
верь в новичковVueДолжно быть, одноклассники наступили на эту яму, изменили индекс массива и не запустили обновление представления.
Например следующий случай:
var vm = new Vue({
data: {
items: ['a', 'b', 'c']
}
})
vm.items[1] = 'x' // 不是响应性的
Выдержки из приведенного выше делаОфициальная документация Vue — Обнаружение обновления массива.
(2) Решение
VueОфициальные документы также даются, используйтеVue.setВы можете добиться эффекта запуска обновления представления.
// Vue.set
Vue.set(vm.items, indexOfItem, newValue);
(3) Почему Vue не может отслеживать изменения индекса?
VueОфициальное объяснение дано и не может быть обнаружено.
Из-за ограничений JavaScript Vue не может обнаружить изменения в следующих массивах: Когда вы напрямую устанавливаете элемент массива по индексу, например:
vm.items[indexOfItem] = newValue.
В чем причина этого? В процессе обучения обнаружил, что многие статьи вырваны из контекста,VueОфициальное объяснение состоит в том, что [Vueне может быть обнаружен], и во многих статьях пишут [Object.definePropertyне может быть обнаружен].
Но по фактуObject.definePropertyМожно обнаружить изменения в индексе массива. Следующий случай:
let data = [1, 2];
function defineReactive (obj, key, val) {
Object.defineProperty(obj, key, {
enumerable: true,
configurable: true,
get: () => {
console.log('我被读了,我要不要做点什么好?');
return val;
},
set: newVal => {
if (val === newVal) {
return;
}
val = newVal;
console.log("数据被改变了,我要渲染到页面上去!");
}
})
}
defineReactive(data, 0, 1);
console.log(data[0]);
data[0] = 5;
Можете сами попробовать в консоли, ответ очень очевиден.
VueПросто этот метод не используется для отслеживания изменений индекса массива, потому что Youda считает, что потребление производительности слишком велико, поэтому он идет на компромисс между производительностью и пользовательским опытом.
Подробности можно найти в этой статьеПочему Vue не может обнаружить изменения массива.
Ну наконец-то раскрыта тайна, почемуVueПочему мы не можем обнаружить изменения массива, потому что мы этого не делаем, ха-ха.
Но у наших разработчиков должен быть этот спрос, решение следующее, используйтеVue.set.
// Vue.set
Vue.set(vm.items, indexOfItem, newValue);
принцип? Очевидно, что в начальном процессе нет цикла, прослушивающего все индексы массива, но разработчику нужно прослушивать, какой индекс.Vue.setЯ помогу вам следить за тем, какой из них, ядро все ещеObject.defineProperty. Просто избегайте бесполезного прослушивания индекса массива, насколько это возможно.
Во-вторых, как следить за увеличением или уменьшением содержимого массива?
(1) Ограничение навыков
Object.definePropertyХотя он может обнаруживать изменения в индексе, он не отслеживает добавление или удаление массива. может читатьОфициальная документация Vue — рекомендации по обнаружению изменений объектачтобы понять.
В настоящее времяVueКак это делается?
(2) Умное решение
VueРешение состоит в том, чтобы переписать прототип массива, более точное выражениеперехватыватьПрототип массива.
Сначала выбираются семь методов, которые могут изменить сам массив. Далее рассмотрим случай:
// 获得原型上的方法
const arrayProto = Array.prototype;
// 创建一个新对象,使用现有的对象来提供新创建的对象的__proto__
const arrayMethods = Object.create(arrayProto);
// 做一些拦截的操作
Object.defineProperty(arrayMethods, 'push', {
value(...args) {
console.log('用户传进来的参数', args);
// 真正的push 保证数据如用户期望
arrayProto.push.apply(this, args);
},
enumerable: true,
writable: true,
configurable: true,
});
let list = [1];
list.__proto__ = arrayMethods; // 重置原型
list.push(2, 3);
console.log('用户得到的list:', list);
почему это называетсяперехватывать, мы переписываем случайpushметод, вам также нужно использовать настоящийpush, чтобы убедиться, что массив соответствует ожиданиям пользователяpushвходить.
Вы можете увидеть следующие эффекты: мы можем не только отслеживать параметры, переданные пользователем, то есть следить за изменением массива, но и следить за тем, чтобы массив соответствовал ожиданиям пользователя.pushвходить.
зачем использоватьarrayMethodsУнаследуйте настоящий прототип, потому что таким образом он не загрязнит глобальнуюArray.prototype, потому что массив, который мы хотим отслеживать, состоит только изvm.dataсередина.
(3) Анализ исходного кода
export class Observer {
constructor (value: any) {
// 如果是数组
if (Array.isArray(value)) {
// 如果原型上有__proto__属性, 主要是浏览器判断兼容
if (hasProto) {
// 直接覆盖响应式对象的原型
protoAugment(value, arrayMethods)
} else {
// 直接拷贝到对象的属性上,因为访问一个对象的方法时,先找他自身是否有,然后才去原型上找
copyAugment(value, arrayMethods, arrayKeys)
}
} else {
// 如果是对象
this.walk(value);
}
}
}
Как видно вышеObserverСпециальная обработка массивов.
(4) Как массив собирает зависимости и распространяет обновления?
Мы знаем, что объект находится вgetter中收集依赖,setter中派发更新.
Просто помни:
function defineReactive (obj, key, val) {
// 生成一个Dep实例
let dep = new Dep();
Object.defineProperty(obj, key, {
get: () => {
// 依赖收集
dep.depend();
},
set: () => {
// 派发更新
dep.notify();
},
})
}
чтобы убедиться, чтоdataКаждые данные имеют однозначноеdep, замыкание применяется здесь, чтобы гарантировать, что каждыйdepЭкземпляры не уничтожаются. Итак, возникает вопрос,depявляется локальной переменной
Чтобы отслеживать изменения массива, вам необходимо отправлять обновления в перехватчик массива. то вы не можете получить доступ к этомуdep, невозможно точно знать, что уведомлятьWatcher!
Так как же Vue это делает? Так как этот доступ недоступен, то другойdepБар.
export class Observer {
constructor (value: any) {
this.value = value // data属性
this.dep = new Dep() // 挂载dep实例
// 为数据定义了一个 __ob__ 属性,这个属性的值就是当前 Observer 实例对象
def(value, '__ob__', this) // 把当前Observer实例挂在到data的__ob__上
}
}
существуетVueВо время инициализации дайтеdataВсе данные монтируются с текущимObserverэкземпляр, который снова монтируется на этот экземплярdep. Это гарантирует, что у нас есть доступ к перехватчику массиваdep. следующим образом:
Object.defineProperty(arrayMethods, 'push', {
value(...args) {
console.log('用户传进来的参数', args);
// 真正的push 保证数据如用户期望
arrayProto.push.apply(this, args);
// this指向当前这个数组,在初始化的时候被赋值__ob__
console.log(this.__ob__.dep)
},
enumerable: true,
writable: true,
configurable: true,
});
Теперь мы можем выполнить в перехватчикеdep.notify()Ла.
Как собрать зависимости?
// 获取当前data上的 observe实例,也就是__ob__
let childOb = !shallow && observe(val);
function defineReactive (obj, key, val) {
// 生成一个Dep实例
let dep = new Dep();
Object.defineProperty(obj, key, {
get: () => {
if (Dep.target) {
// 依赖收集
dep.depend();
// 二次收集
if (childOb.dep) {
// 再收集一次依赖
childOb.dep.depend();
}
}
return val;
},
})
}
Теперь для хранения 2dep, тогда конечноgetterсобирал 2 раза,childObНа самом деле этоobserveвернулся в__ob__. Не заботьтесь о деталях, просто проверьте исходный код, чтобы узнать ~
(5) Резюме
Подводя итог, для массивов在getter中收集依赖,在拦截器中触发更新.
3. Другие мысли
(1) Размышление: где еще можно использовать __ob__?
-
Определите, был ли массив Observed, чтобы избежать повторного выполнения.
-
Vue.setа такжеVue.del, все требуют доступаdepиз.
(2) Считается ли присвоение массива изменением длины?
потому чтоObject.definePropertyИзменения длины массива не могут быть обнаружены, например:vm.items.length = newLength.
var vm = new Vue({
data: {
items: ['a']
}
})
// 重新赋值,改变长度
vm.items = ['a, 'b', 'c']
Этоvm.items = ['a, 'b', 'c']В этом случае,VueКак это контролируется? В этом случае на самом деле слушает объект.vmизitemsАтрибуты не имеют ничего общего с массивами. Поскольку я обнаружил, что кого-то неправильно поняли, вот простое напоминание~
4. Резюме
Эта статья в основном фокусируется на принципах и идеях и не требует много кода, ведь исходный код всегда будет меняться. В то же время вы также должны убедиться, что ваш js-фундамент надежен, поэтому чтение исходного кода не составит труда ~ Я тот, кому очень сложно 😭
Если вы считаете, что это полезно для вас, пожалуйста, поставьте лайк~
завершенный:
Серия интерпретаций исходного кода Vue
- 1. Реактивный принцип Vue — понимание Observer, Dep, Watcher
- 2. Принцип отзывчивости — как следить за изменениями массива
- 3. Принцип отзывчивости - как следить за изменениями массива? Подробная версия
- 4. Асинхронное обновление Vue — почему nextTick сначала выполняет микрозадачу?
Блог на гитхабеДобро пожаловать на общение~
5. Ссылки
- Помните вопрос, чтобы подумать, задавать ли вопросы: Почему Vue не может обнаружить изменения массива
- Книга "Введение в Vue.js"