Аннотация: Цикл статей о паттернах проектирования интерпретации исходного кода будет обновляться одна за другой~
Шаблон наблюдателя
Прежде всего, мы должны спросить себя, что такое паттерн наблюдателя?
концепция
Шаблон наблюдателя (наблюдатель): также известен как шаблон публикации-подписчика. Он определяет отношение зависимости «один ко многим», то есть при изменении состояния объекта все объекты, которые зависят от него, будут уведомлены и автоматически обновлены, что устраняет функциональную связь между объектом-субъектом и наблюдателем.
Расскажи историю
Вышеупомянутая концепция шаблона наблюдателя может быть более официальной, поэтому давайте расскажем историю, чтобы понять ее.
- О: Это шпион, кодовое имя 001 (издатель).
- B: является корреспондентом, ответственным за секретную передачу с A (абонентом)
- Ежедневная работа А состоит в том, чтобы собрать некоторую информацию о яркой стороне
- B отвечает за темновое наблюдение A
- После того, как A передает некоторые релевантные сообщения (чаще сообщение необходимо инкапсулировать и передать, а конкретный анализ будет основан на исходном коде позже)
- B немедленно подпишется на сообщение, а затем внесет некоторые соответствующие изменения, такие как уведомление о выполнении некоторых действий для обработки некоторых действий.
применимость
Шаблон Observer можно использовать в любом из следующих сценариев.
- Когда абстрактная модель имеет два аспекта, один зависит от другого. Инкапсуляция двух в отдельные объекты позволяет их независимо изменять и повторно использовать.
- При изменении одного объекта необходимо одновременно изменить и другие объекты, но неизвестно, сколько объектов нужно изменить.
- Когда объект должен уведомить другие объекты, но не знает, кто этот конкретный объект. Другими словами, вы не хотите, чтобы эти объекты были тесно связаны.
Использование vue для шаблона Observer
vue
Есть много мест, где можно использовать шаблон наблюдателя, здесь мы в основном говорим об инициализации данных.
var vm = new Vue({
data () {
return {
a: 'hello vue'
}
}
})
1. Реализовать захват данных
Выше мы видим, чтоvue
используетObject.defineProperty()
Взлом данных. И инкапсулировать слой транзитной станции при изменении передачи данных, то есть то, что мы видимDep
а такжеWatcher
два класса.
В этом разделе мы рассмотрим только то, как перехватить данные с помощью шаблона наблюдателя.
1.1, рекурсивный обход
мы все знаем,vue
дляdata
Все данные в нем захвачены, он может только пройти через объект, чтобы завершить захват каждого атрибута.Исходный код выглядит следующим образом
walk (obj: Object) {
const keys = Object.keys(obj)
// 遍历将其变成 vue 的访问器属性
for (let i = 0; i < keys.length; i++) {
defineReactive(obj, keys[i], obj[keys[i]])
}
}
1.2. Публикация/подписка
Из обхода вышеуказанных объектов мы видимdefineReactive
, то наиболее критической точкой угона также является эта функция, которая инкапсулируетgetter
а такжеsetter
Функция, использующая шаблон наблюдателя, слушающая друг друга
// 设置为访问器属性,并在其 getter 和 setter 函数中,使用发布/订阅模式,互相监听。
export function defineReactive (
obj: Object,
key: string,
val: any
) {
// 这里用到了观察者(发布/订阅)模式进行了劫持封装,它定义了一种一对多的关系,让多个观察者监听一个主题对象,这个主题对象的状态发生改变时会通知所有观察者对象,观察者对象就可以更新自己的状态。
// 实例化一个主题对象,对象中有空的观察者列表
const dep = new Dep()
// 获取属性描述符对象(更多的为了 computed 里面的自定义 get 和 set 进行的设计)
const property = Object.getOwnPropertyDescriptor(obj, key)
if (property && property.configurable === false) {
return
}
const getter = property && property.get
const setter = property && property.set
let childOb = 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) {
childOb.dep.depend()
// 这里是对数组进行劫持
if (Array.isArray(value)) {
dependArray(value)
}
}
}
return value
},
// 劫持到数据变更,并发布消息进行通知
set: function reactiveSetter (newVal) {
const value = getter ? getter.call(obj) : val
if (newVal === value || (newVal !== newVal && value !== value)) {
return
}
if (setter) {
setter.call(obj, newVal)
} else {
val = newVal
}
childOb = observe(newVal)
dep.notify()
}
})
}
1.3. Возврат экземпляра Observer
выше мы видимobserve
функция, ядро состоит в том, чтобы вернутьObserver
Пример
return new Observer(value)
2. Инкапсуляция сообщений для реализации «транзитной станции».
Прежде всего, нам нужно понять, зачем нам инкапсулировать слой передачи сообщений?
Мы упомянули об этом, когда объясняли шаблон наблюдателя.适用性
. То же самое и здесь: когда мы перехватываем изменение данных и уведомляем об изменении данных, если мы не делаем «транзитную станцию», мы не знаем, кто подписался на сообщение и сколько объектов подписалось на сообщение.
Это как агенты A (издатель) и B (подписчик) в истории, которую я упоминал выше. Агент А и Б передают информацию. Оба знают о существовании такого человека, как другой, но Агент А не знает, кто такой Б и сколько (подписчиков) на него подписано. Многие из них могут подписаться на агента А. информации, поэтому агент А (издатель) должен передать暗号
Соберите всех (подписчиков), которые подписываются на его сообщения, здесь сбор подписчиков на самом деле является слоем封装
. Затем шпион А просто публикует сообщение, а подписчики получают уведомление и просто делают своеupdate
Просто сделай это.
Если быть проще, то шпион А, набравший подписчиков, просто публикует сообщение, а Б и больше просто подписываются на сообщение и делают соответствующиеupdate
работы каждый модуль обеспечивает свою независимость, реализуя高内聚低耦合
эти два принципа.
Без дальнейших церемоний, давайте поговорим о том, как vue выполняет инкапсуляцию сообщений.
2.1 Деп
Dep
, полное название Зависимость, мы также можем примерно увидеть из названияDep
Классы используются для сбора зависимостей, как их собирать. Давайте посмотрим непосредственно на исходный код
let uid = 0
export default class Dep {
static target: ?Watcher;
id: number;
subs: Array<Watcher>;
constructor () {
// 用来给每个订阅者 Watcher 做唯一标识符,防止重复收集
this.id = uid++
// 定义subs数组,用来做依赖收集(收集所有的订阅者 Watcher)
this.subs = []
}
// 收集订阅者
addSub (sub: Watcher) {
this.subs.push(sub)
}
depend () {
if (Dep.target) {
Dep.target.addDep(this)
}
}
notify () {
// stabilize the subscriber list first
const subs = this.subs.slice()
for (let i = 0, l = subs.length; i < l; i++) {
subs[i].update()
}
}
}
// the current target watcher being evaluated.
// this is globally unique because there could be only one
// watcher being evaluated at any time.
Dep.target = null
Код короткий, но то, что он делает, важно
- Определите массив подписок для сбора подписчиков Watchers
- Когда изменения данных перехватываются, подписчик-наблюдатель уведомляется о необходимости выполнить операцию обновления.
В исходном коде также брошены два метода для работыDep.target
, подробности следующим образом
// 定义收集目标栈
const targetStack = []
export function pushTarget (_target: Watcher) {
if (Dep.target) targetStack.push(Dep.target)
// 改变目标指向
Dep.target = _target
}
export function popTarget () {
// 删除当前目标,重算指向
Dep.target = targetStack.pop()
}
2.2. Наблюдатель
Watcher
Имеется в виду наблюдатель, за что он отвечает, так это за подпискуDep
,когдаDep
отправить сообщение прохождение (notify
), так что подписывайтесьDep
изWatchers
сделает своеupdate
работать. Ничего особенного, просто посмотрите исходный код, чтобы узнать.
export default class Watcher {
vm: Component;
expression: string;
cb: Function;
constructor (
vm: Component,
expOrFn: string | Function,
cb: Function,
options?: Object
) {
this.vm = vm
vm._watchers.push(this)
this.cb = cb
// parse expression for getter
if (typeof expOrFn === 'function') {
this.getter = expOrFn
} else {
// 解析表达式
this.getter = parsePath(expOrFn)
if (!this.getter) {
this.getter = function () {}
}
}
this.value = this.get()
}
get () {
// 将目标收集到目标栈
pushTarget(this)
const vm = this.vm
let value = this.getter.call(vm, vm)
// 删除目标
popTarget()
return value
}
// 订阅 Dep,同时让 Dep 知道自己订阅着它
addDep (dep: Dep) {
const id = dep.id
if (!this.newDepIds.has(id)) {
this.newDepIds.add(id)
this.newDeps.push(dep)
if (!this.depIds.has(id)) {
// 收集订阅者
dep.addSub(this)
}
}
}
// 订阅者'消费'动作,当接收到变更时则会执行
update () {
this.run()
}
run () {
const value = this.get()
const oldValue = this.value
this.value = value
this.cb.call(this.vm, value, oldValue)
}
}
В приведенном выше коде я удалил некоторые коды, не относящиеся к текущему обсуждению.Если вам нужно провести подробное исследование, вы можете проверить исходный код vue2.5.3 самостоятельно.
посмотри еще раз сейчасDep
а такжеWatcher
, нам нужно знать две точки
-
Dep
Отвечает за сбор всех подписчиковWatcher
, вам не нужно контролировать кого и сколько, вам просто нужно пройтиtarget
Указывает на вычисление, чтобы собрать подписано на свои сообщенияWatcher
Вот и все, тогда просто хорошо поработайте над публикацией новостейnotify
Вот и все. -
Watcher
Ответственный за подпискуDep
, а при подписке пустьDep
собирать, получатьDep
Публикуя сообщение, делайте это хорошоupdate
Просто сделай это.
Они кажутся зависимыми друг от друга, но на самом деле они обеспечивают их независимость и единство модулей.
больше приложений
vue
Есть также некоторые места, где используется «универсальный»观察者模式
, такие как доставка событий между компонентами, с которыми мы знакомы,$on
так же как$emit
дизайн.
$emit
Отвечает за публикацию сообщений и общение с подписчиками$on
Унифицированное потребление, то есть выполнениеcbs
Все события внутри.
Vue.prototype.$on = function (event: string | Array<string>, fn: Function): Component {
const vm: Component = this
if (Array.isArray(event)) {
for (let i = 0, l = event.length; i < l; i++) {
this.$on(event[i], fn)
}
} else {
(vm._events[event] || (vm._events[event] = [])).push(fn)
}
return vm
}
Vue.prototype.$emit = function (event: string): Component {
const vm: Component = this
let cbs = vm._events[event]
if (cbs) {
cbs = cbs.length > 1 ? toArray(cbs) : cbs
const args = toArray(arguments, 1)
for (let i = 0, l = cbs.length; i < l; i++) {
cbs[i].apply(vm, args)
}
}
return vm
}
Суммировать
В этой статье обсуждаются основные концепции, применимые сценарии и конкретные приложения шаблона наблюдателя в исходном коде vue. В этом разделе будут обобщены некоторые преимущества и недостатки шаблона Observer.
- Абстрактная связь между целью и наблюдателем: цель знает только, что у нее есть ряд наблюдателей (цель выполняет сбор зависимостей), но не знает, к какому конкретному классу принадлежит любой наблюдатель, поэтому связь между целью и наблюдателем является абстрактной и минимальный.
- Поддержка широковещательной связи: для связи в наблюдателе, в отличие от других распространенных запросов, необходимо указать получателя. Уведомление будет автоматически транслироваться на все связанные объекты, подписавшиеся на целевой объект, то есть вышеперечисленные
dep.notify()
. Конечно, целевому объекту все равно, сколько объектов в нем заинтересовано. Его единственная обязанность — уведомить своих наблюдателей. - Некоторые неожиданные обновления: поскольку сам наблюдатель не знает о существовании других наблюдателей, он может быть невежественным из окончательной стоимости изменения цели. Если наблюдатель работает непосредственно на цели, он может привести к серии обновлений наблюдателя и те объекты, которые зависят от этих наблюдателей, поэтому обычно мы приведем некоторые операции внутри цели, чтобы предотвратить вышеуказанные проблемы.
Хорошо, эта статья почти готова, и более подробные идеи дизайна исходного кода будут объяснены одна за другой в других статьях той же серии.
Лично я готов снова забрать свой официальный аккаунт.После этого гарантирую качественную и хорошую статью каждую неделю.Заинтересованные друзья могут обратить на нее внимание.