Tabla de contenido
prefacio
El núcleo de Vue.Js incluye un "sistema receptivo". "Sensible" significa que cuando los datos cambian, Vue notificará al código que usa los datos. Por ejemplo, los datos se utilizan en la representación de vistas y la vista se actualiza automáticamente cuando cambian los datos.
1. Búsqueda de entrada
La capacidad de respuesta de datos de vue2.X se realiza utilizando para interceptar la adquisición y configuración de propiedades Object.defineProperty()
mediante la definición de propiedades de objetos . ¿Cómo lograrlo? En primer lugar, debe considerar por dónde empezar a ver el código fuente. En el artículo anterior Aprendizaje del código fuente de Vue: ¿qué hizo la nueva inicialización de Vue? Hablando de un método llamado: , la explicación que se le dio en ese momento fue: inicializar accesorios, métodos, datos, computar, observar, incluido el procesamiento receptivo.getter/setter
initState()
// src/core/instance/init.ts
export function initMixin(Vue: typeof Component) {
// 在原型上添加 _init 方法
Vue.prototype._init = function (options?: Record<string, any>) {
const vm: Component = this
//...
vm._self = vm
initLifecycle(vm) // 初始化实例的属性、数据:$parent, $children, $refs, $root, _watcher...等
initEvents(vm) // 初始化事件:$on, $off, $emit, $once
initRender(vm) // 初始化渲染: render, mixin
callHook(vm, 'beforeCreate', undefined, false) // // 调用生命周期的钩子函数,在这里就能看出一个组件在创建之前和之后分别做了哪些初始化
initInjections(vm) // 初始化 inject
initState(vm) // 对props,methods,data,computed,watch进行初始化,包括响应式的处理
initProvide(vm) // 初始化 provide
callHook(vm, 'created') // created 初始化完成,可以执行挂载了
//...
}
}
La inicialización llama a muchos métodos aquí, y cada método hace cosas diferentes, y lo principal sobre la capacidad de respuesta son los datos en el componente props、data
. El contenido de esta pieza está en initState()
este método, así que ingrese el código fuente de este método para echar un vistazo.
2. Inicialización
initState()
// src/core/instance/state.ts
// 数据响应式的入口
export function initState(vm: Component) {
const opts = vm.$options
// 初始化 props
if (opts.props) initProps(vm, opts.props)
// 初始化 methods
if (opts.methods) initMethods(vm, opts.methods)
// 初始化 data
if (opts.data) {
initData(vm)
} else {
// 没有 data 的话就默认赋值为空对象,并监听
const ob = observe((vm._data = {
}))
ob && ob.vmCount++
}
// 初始化 computed
if (opts.computed) initComputed(vm, opts.computed)
// 初始化 watch
if (opts.watch && opts.watch !== nativeWatch) {
initWatch(vm, opts.watch)
}
}
Esto también es un montón de cosas de inicialización. Vayamos directamente al tema y relacionémonos con los datos receptivos, es decir initProps()
, , initData()
, observe()
, y luego miremos el código fuente uno por uno.
initProps()
Estas son las principales cosas que hacer:
- Recorra la lista pasada por el componente padre
props
. - Verifique el nombre, tipo, atributo predeterminado, etc. de cada atributo. Si no hay problema, llame y
defineReactive
configúrelo para que responda. - Luego, use para
proxy()
enviar la propiedad a la instancia actual, como cambiarvm._props.xx
avm.xx
, puede acceder a ella.
// src/core/instance/state.ts
function initProps(vm: Component, propsOptions: Object) {
// 父组件传入子组件的 props
const propsData = vm.$options.propsData || {
}
// 经过转换后最终的 props
const props = (vm._props = shallowReactive({
}))
// 存放 props 的数组
const keys: string[] = (vm.$options._propKeys = [])
const isRoot = !vm.$parent
// 转换非根实例的 props
if (!isRoot) {
toggleObserving(false)
}
for (const key in propsOptions) {
keys.push(key)
// 校验 props 类型、default 属性等
const value = validateProp(key, propsOptions, propsData, vm)
// 非生产环境下
if (__DEV__) {
const hyphenatedKey = hyphenate(key)
if (
isReservedAttribute(hyphenatedKey) ||
config.isReservedAttr(hyphenatedKey)
) {
warn(
`"${
hyphenatedKey}" is a reserved attribute and cannot be used as component prop.`,
vm
)
}
// 把 props 设置成响应式的
defineReactive(props, key, value, () => {
// 如果用户修改子组件中的 props 发出警告
if (!isRoot && !isUpdatingChildComponent) {
warn( `xxx警告`,vm)
}
})
} else {
// 把 props 设置成响应式的
defineReactive(props, key, value)
}
// 把不在默认 vm 上的属性,代理到实例上
// 可以让 vm._props.xx 通过 vm.xx 访问
if (!(key in vm)) {
proxy(vm, `_props`, key)
}
}
toggleObserving(true)
}
- List item
initData()
Estas son las principales cosas que hacer:
- Inicialice un dato y obtenga la colección de claves.
- Recorra la colección de claves para determinar si hay un nombre idéntico al nombre de la propiedad en accesorios o al nombre del método en métodos.
- Si no hay ningún problema, use proxy() para enviar todos los atributos de los datos a la instancia actual, y luego puede
this.xx
acceder a ellos a través de . - Finalmente, llame para
observe
monitorear todos los datos.
// src/core/instance/state.ts
function initData(vm: Component) {
// 获取当前实例的 data
let data: any = vm.$options.data
// 判断 data 类型
data = vm._data = isFunction(data) ? getData(data, vm) : data || {
}
if (!isPlainObject(data)) {
data = {
}
__DEV__ && warn('数据函数应该返回一个对象',vm)
}
// 获取当前实例的 data 属性名集合
const keys = Object.keys(data)
// 获取当前实例的 props
const props = vm.$options.props
// 获取当前实例的 methods 对象
const methods = vm.$options.methods
let i = keys.length
while (i--) {
const key = keys[i]
// 非生产环境下判断 methods 里的方法是否存在于 props 中
if (__DEV__) {
if (methods && hasOwn(methods, key)) {
warn(`Method方法不能重复声明`, vm)
}
}
// 非生产环境下判断 data 里的属性是否存在于 props 中
if (props && hasOwn(props, key)) {
__DEV__ &&
warn(`属性不能重复声明`,vm)
} else if (!isReserved(key)) {
// 都不重名的情况下,代理到 vm 上
// 可以让 vm._data.xx 通过 vm.xx 访问
proxy(vm, `_data`, key)
}
}
// observe 监听 data
const ob = observe(data)
ob && ob.vmCount++
}
observe() - Guardia del observador
Este método se utiliza principalmente para agregar oyentes a los datos.
Estrictamente hablando, el método observe() debe considerarse como el guardián del Observer, que es una detección de cumplimiento antes de que el Observer esté a punto de comenzar.
Estas son las principales cosas que hacer:
- Si el valor ya se ha procesado de forma reactiva (ya se ha observado), devuelve el observador existente.
- De lo contrario, se agrega una nueva instancia de observador para el valor que no tiene observador agregado.
// src/core/observer/index.ts
export function observe(
value: any,
shallow?: boolean,
ssrMockReactivity?: boolean
): Observer | void {
// 首先'__ob__'的值其实就是一个'Observer'实例
// 所以下面的判断其实就是:如果已经做过了响应式处理(已经被观察过了),则直接返回'ob',也就是'Observer'实例
if (value && hasOwn(value, '__ob__') && value.__ob__ instanceof Observer) {
return value.__ob__
}
// 如果是初始化的时候,则没有'Observer'的实例,因此需要创建一个'Observer'实例
if (
shouldObserve &&
(ssrMockReactivity || !isServerRendering()) &&
(isArray(value) || isPlainObject(value)) &&
Object.isExtensible(value) &&
!value.__v_skip /* ReactiveFlags.SKIP */ &&
!isRef(value) &&
!(value instanceof VNode)
) {
return new Observer(value, shallow, ssrMockReactivity)
}
}
Observador
Esta es una clase cuyo rol es convertir un dato normal en un dato observable.
Estas son las principales cosas que hacer:
- Marque el valor actual como un atributo de respuesta para evitar operaciones repetidas.
- Luego juzgue el tipo de datos
1) En caso afirmativo数组
, recorra la matriz y llameobserve()
para monitorear cada elemento.
2) En caso afirmativo对象
, recorra el objeto y llame paradefineReactive()
crear un objeto receptivo.
// src/core/observer/index.ts
export class Observer {
dep: Dep
vmCount: number
constructor(public value: any, public shallow = false, public mock = false) {
// 实例化一个dep
// 正常来说,遍历一个对象的属性时,都是一个属性创建一个dep,为什么此处要给当前对象额外创建一个dep?
// 其目的在于如果使用Vue.set/delete添加或删除属性,这个dep负责通知更新。
this.dep = mock ? mockDep : new Dep()
this.vmCount = 0
// 给 value 添加 __ob__ 属性,值为value的 Observe 实例
// 表示已经变成响应式了,目的是对象遍历时就直接跳过,避免重复操作
def(value, '__ob__', this)
// 类型判断
if (isArray(value)) {
if (!mock) {
// 判断数组是否有__proto__
if (hasProto) {
// 如果有就重写数组的方法
;(value as any).__proto__ = arrayMethods
} else {
// 没有就通过 def,也就是Object.defineProperty 去定义属性值
for (let i = 0, l = arrayKeys.length; i < l; i++) {
const key = arrayKeys[i]
def(value, key, arrayMethods[key])
}
}
}
if (!shallow) {
this.observeArray(value)
}
} else {
// 对象响应式处理方法
const keys = Object.keys(value)
for (let i = 0; i < keys.length; i++) {
const key = keys[i]
defineReactive(value, key, NO_INITIAL_VALUE, undefined, shallow, mock)
}
}
}
observeArray(value: any[]) {
// 遍历数组,为数组的每一项设置观察,处理数组元素为对象的情况
for (let i = 0, l = value.length; i < l; i++) {
observe(value[i], false, this.mock)
}
}
definirReactivo()
El rol es definir objetos sensibles.
Estas son las principales cosas que hacer:
- Cree uno para cada atributo primero
dep 实例
. - Si es
对象
,observe()
llámelo y escuche recursivamente para asegurarse de que no importa cuán profunda esté anidada la estructura, las propiedades internas pueden convertirse en objetos receptivos. - Luego llame
Object.defineProperty()
al getter y setter del objeto secuestrado. - Si se obtiene, el captador de activación llamará
dep.depend()
parawatcher(观察者)
enviar a la matriz dependientesubs
. - Si se actualiza, activar el setter hará lo siguiente:
1) El nuevo valor no cambia o salta directamente sin la propiedad setter.
2) Si el nuevo valor es un objeto, llameobserve()
al oyente recursivo.
3) Luego llame adep.notify()
la actualización de despacho.
// src/core/observer/index.ts
export function defineReactive(
obj: object,
key: string,
val?: any,
customSetter?: Function | null,
shallow?: boolean,
mock?: boolean
) {
// 给每个响应式数据的 属性 都对应着一个 Dep 实例(重要)
const dep = new Dep()
// 拿到对象的属性描述符
const property = Object.getOwnPropertyDescriptor(obj, key)
if (property && property.configurable === false) {
return
}
// 获取自定义的 getter 和 setter
const getter = property && property.get
const setter = property && property.set
if (
(!getter || setter) &&
(val === NO_INITIAL_VALUE || arguments.length === 2)
) {
val = obj[key]
}
// 如果 val 是对象的话就递归监听
// 递归调用 observe 就可以保证不管对象结构嵌套有多深,都能变成响应式对象
let childOb = !shallow && observe(val, false, mock)
// 截持对象属性的 getter 和 setter
Object.defineProperty(obj, key, {
enumerable: true,
configurable: true,
// 拦截 getter,当取值时会触发该函数
get: function reactiveGetter() {
const value = getter ? getter.call(obj) : val
// 进行依赖收集
// 初始化渲染 watcher 时访问到需要双向绑定的对象,从而触发 get 函数
if (Dep.target) {
if (__DEV__) {
dep.depend({
target: obj,
type: TrackOpTypes.GET,
key
})
} else {
dep.depend()
}
if (childOb) {
childOb.dep.depend()
if (isArray(value)) {
dependArray(value)
}
}
}
return isRef(value) && !shallow ? value.value : value
},
// 拦截 setter,当值改变时会触发该函数
set: function reactiveSetter(newVal) {
const value = getter ? getter.call(obj) : val
// 判断新值是否发生变化
if (!hasChanged(value, newVal)) {
return
}
if (__DEV__ && customSetter) {
customSetter()
}
if (setter) {
setter.call(obj, newVal)
} else if (getter) {
return
} else if (!shallow && isRef(value) && !isRef(newVal)) {
value.value = newVal
return
} else {
val = newVal
}
// 如果新值是对象的话递归监听,也做响应式处理
childOb = !shallow && observe(newVal, false, mock)
if (__DEV__) {
// 派发更新
dep.notify({
type: TriggerOpTypes.SET,
target: obj,
key,
newValue: newVal,
oldValue: value
})
} else {
// 派发更新
dep.notify()
}
}
})
return dep
}
El código fuente anterior presenta dep.depend
la recopilación de dependencias y dep.notify()
la distribución de actualizaciones a través de .
Se puede decir que el papel de Dep en la capacidad de respuesta de datos es 数据的依赖收集
y 变更通知
.
3. Confíe en la colección
El núcleo de la colección de dependencias es Dep
, y también es Watcher
inseparable de . A continuación se explicará a través de ejemplos y código fuente.
Watcher y Dep obtienen una idea de conceptos a través de ejemplos
1) ¿Qué es el Vigilante?
Por favor, vea el código a continuación:
// 例子代码,与本章代码无关
<div>{
{
name }}</div>
data() {
return {
name: '铁锤妹妹'
}
},
computed: {
info () {
return this.name
}
},
watch: {
name(newVal) {
console.log(newVal)
}
}
Del código anterior, podemos ver que name
las variables dependen de tres lugares, que son html里
, computed里
y watch里
. Siempre que name
cambie el valor del atributo, se volverá a representar en html, se recalculará en computado y se volverá a ejecutar en observación. Entonces, ¿quién va a notificar a estos tres lugares name
de modificación? Eso es Watcher
todo.
2) ¿Cuáles son los tipos de Vigilante?
Los tres lugares mencionados anteriormente solo representan tres tipos Watcher
, a saber:
- Rendering Watcher : Cuando se modifica la variable, se encarga de notificar la re-renderización en HTML.
- Computed Watcher : Cuando se modifica una variable, se encarga de notificar los cambios de las variables de atributos computados en computados que dependen de esta variable.
- usuario Watcher : Cuando se modifica una variable, se encarga de notificar la ejecución de la función de la variable correspondiente en el atributo watch.
3) ¿Qué es Dep?
¿Qué es Dep? Todavía el código de ejemplo anterior:
<div>{
{
name }}</div>
data() {
return {
name: '铁锤妹妹'
}
},
computed: {
info () {
return this.name
}
},
watch: {
name(newVal) {
console.log(newVal)
}
}
Las variables aquí name
dependen de tres lugares, y los tres lugares representan tres tipos Watcher
, entonces, ¿ name
manejará estos tres directamente Watcher
? La respuesta es no, name
se creará una instancia de un Dep para administrarlos por mí mismo Wacther
, similar a un ama de llaves, cuando name
se realicen cambios, se notificará al dep, y el dep tomará la orden del maestro para notificarlos Wacther
para completar lo que debe hacer. .
Después de entender el ejemplo anterior, veamos el código fuente de Dep y Watcher a continuación .
Código fuente de la dependencia
Esta es una clase que realmente Watcher
administra el archivo .
Primero inicialice una subs
matriz para almacenar dependencias, es decir, observadores; quien dependa de estos datos está en esta matriz; luego defina varios métodos para agregar, eliminar y notificar actualizaciones a las dependencias .
Además, Dep también tiene un atributo estático target
, que es global Watcher
, lo que también significa que solo puede existir uno global a la vez Watcher
.
- Debido a que involucra la parte de profundidad, solo eche un vistazo a esto, primero comprenda el proceso aproximadamente, y hablaré de eso en detalle a continuación, que tiene algo que ver con eso.
- Cada propiedad monitoreada tendrá una
Dep
instancia correspondiente. En el getter de la propiedad, el Watcher que se está ejecutando actualmente se agregará a la lista de dependencias del Dep. Cuando la propiedad cambie,notify
todos los Vigilantes dependientes serán notificados para actualizar a través del método de Dep.- Durante el proceso de actualización, el método setter del atributo se activará primero, y luego la instancia Dep correspondiente al atributo notificará a todos los objetos Watcher dependientes para que se actualicen. Estos objetos de Watcher se agregarán a
调度器队列
, específicamente llamando alupdate
método de Watcher para agregar Watcher a调度器队列
. Finalmente, Vue ejecutará la operación de actualización del programador en el momento adecuado, sacará el Watcher de la cola del programador y ejecutará su lógica de actualización.
// src/core/observer/dep.ts
// Dep在数据响应式中扮演的角色就是数据的 依赖收集 和 变更通知
// 在获取数据的时候知道自己(Dep)依赖的watcher都有谁,同时在数据变更的时候通知自己(Dep)依赖的这些watcher去执行他们(watcher)的update
export default class Dep {
static target?: DepTarget | null
id: number
subs: Array<DepTarget | null>
_pending = false
constructor() {
this.id = uid++
// 用来存储 Watcher 的数组
this.subs = []
}
// 在 dep 中添加 观察者
addSub(sub: DepTarget) {
this.subs.push(sub)
}
// 移除观察者
removeSub(sub: DepTarget) {
this.subs[this.subs.indexOf(sub)] = null
if (!this._pending) {
this._pending = true
pendingCleanupDeps.push(this)
}
}
depend(info?: DebuggerEventExtraInfo) {
if (Dep.target) {
// 调用 watcher 的 addDep 函数
Dep.target.addDep(this)
if (__DEV__ && info && Dep.target.onTrack) {
Dep.target.onTrack({
effect: Dep.target,
...info
})
}
}
}
// 遍历 dep 中所有的 Watcher,通知相关的 Watcher 执行 update 派发更新
notify(info?: DebuggerEventExtraInfo) {
const subs = this.subs.filter(s => s) as DepTarget[]
if (__DEV__ && !config.async) {
subs.sort((a, b) => a.id - b.id)
}
for (let i = 0, l = subs.length; i < l; i++) {
const sub = subs[i]
if (__DEV__ && info) {
sub.onTrigger &&
sub.onTrigger({
effect: subs[i],
...info
})
}
sub.update()
}
}
}
// 同一时间只有一个观察者使用,赋值观察者
Dep.target = null
const targetStack: Array<DepTarget | null | undefined> = []
// 开始收集的时候 设置:Dep.target = watcher
export function pushTarget(target?: DepTarget | null) {
targetStack.push(target)
Dep.target = target
}
// 结束收集的时候 设置:Dep.target = null
export function popTarget() {
targetStack.pop()
Dep.target = targetStack[targetStack.length - 1]
}
Código fuente del observador
Watcher también es una clase, también llamado observador (suscriptor), el trabajo aquí es bastante complicado y también está conectado en 模板编译
serie 渲染
.
En un componente, cada propiedad monitoreada corresponde a una Watcher
instancia. Cuando una propiedad cambia, Watcher
se agregará la correspondiente 调度器队列
(también conocida como la cola de procesamiento).
// src/core/observer/watcher.ts
export default class Watcher implements DepTarget {
// ...
constructor(
vm: Component | null,
expOrFn: string | (() => any),
cb: Function,
options?: WatcherOptions | null,
isRenderWatcher?: boolean
) {
// ...
if ((this.vm = vm) && isRenderWatcher) {
vm._watcher = this
}
if (options) {
this.deep = !!options.deep
this.user = !!options.user
this.lazy = !!options.lazy
this.sync = !!options.sync
this.before = options.before
if (__DEV__) {
this.onTrack = options.onTrack
this.onTrigger = options.onTrigger
}
} else {
this.deep = this.user = this.lazy = this.sync = false
}
this.cb = cb
this.id = ++uid
this.active = true
this.post = false
this.dirty = this.lazy
// Watcher 实例持有的 Dep 实例的数组
this.deps = []
this.newDeps = []
this.depIds = new Set()
this.newDepIds = new Set()
this.expression = __DEV__ ? expOrFn.toString() : ''
if (isFunction(expOrFn)) {
this.getter = expOrFn
} else {
this.getter = parsePath(expOrFn)
if (!this.getter) {
this.getter = noop
__DEV__ &&
warn(
`Failed watching path: "${
expOrFn}" ` +
'Watcher only accepts simple dot-delimited paths. ' +
'For full control, use a function instead.',
vm
)
}
}
this.value = this.lazy ? undefined : this.get()
}
get() {
// 该函数用于缓存 Watcher
// 因为在组件含有嵌套组件的情况下,需要恢复父组件的 Watcher
pushTarget(this)
let value
const vm = this.vm
try {
// 调用回调函数,也就是upcateComponent,对需要双向绑定的对象求值,从而触发依赖收集
value = this.getter.call(vm, vm)
} catch (e: any) {
if (this.user) {
handleError(e, vm, `getter for watcher "${
this.expression}"`)
} else {
throw e
}
} finally {
// 深度监听
if (this.deep) {
traverse(value)
}
// 恢复Watcher
popTarget()
// 清理不需要了的依赖
this.cleanupDeps()
}
return value
}
// 添加依赖
addDep(dep: Dep) {
const id = dep.id
if (!this.newDepIds.has(id)) {
// watcher添加它和dep的关系
this.newDepIds.add(id)
this.newDeps.push(dep)
if (!this.depIds.has(id)) {
// 和上面的反过来,dep添加它和watcher的关系
// 把当前 Watcher push 进 subs 数组
dep.addSub(this)
}
}
}
// 清理不需要的依赖
cleanupDeps() {
}
// 派发更新时调用
update() {
// 如果是懒执行走这里,比如:computed
if (this.lazy) {
this.dirty = true
// 如果是同步执行 则执行run函数
} else if (this.sync) {
this.run()
// 将watcher放到watcher队列中
} else {
queueWatcher(this)
}
}
// 执行 watcher 的回调
run() {
if (this.active) {
// 调用get方法
const value = this.get()
if (
value !== this.value ||
isObject(value) ||
this.deep
) {
// 更换旧值为新值
const oldValue = this.value
this.value = value
if (this.user) {
const info = `callback for watcher "${
this.expression}"`
invokeWithErrorHandling(
this.cb,
this.vm,
[value, oldValue],
this.vm,
info
)
} else {
// 渲染watcher
this.cb.call(this.vm, value, oldValue)
}
}
}
}
// 懒执行的watcher会调用该方法 比如:computed
evaluate() {
this.value = this.get()
// computed的缓存原理
// this.dirty设置为false 则页面渲染时只会执行一次computed的回调
// 数据更新以后 会在update中重新设置为true
this.dirty = false
}
depend() {
let i = this.deps.length
while (i--) {
this.deps[i].depend()
}
}
}
Reponer:
- ¿Por qué el control del reloj escrito en nuestros propios componentes puede obtener automáticamente el valor nuevo y el valor anterior? De hecho,
Watcher.run()
ejecutará y soltará la función, y pasará el valor nuevo y el valor anterior.
Proceso de recopilación de dependencias
Al renderizar y montar por primera vez, habrá una lógica de código de este tipo.
// src/core/instance/lifecycle.ts
export function mountComponent(...): Component {
// 调用生命周期钩子函数
callHook(vm, 'beforeMount')
let updateComponent
// 创建一个更新渲染函数; 调用 _update 对 render 返回的虚拟 DOM 进行 patch(也就是 Diff )到真实DOM,这里是首次渲染
updateComponent = () => {
vm._update(vm._render(), hydrating)
}
// 当触发更新的时候,会在更新之前调用
const watcherOptions: WatcherOptions = {
before() {
// 判断 DOM 是否是挂载状态,就是说首次渲染和卸载的时候不会执行
if (vm._isMounted && !vm._isDestroyed) {
// 调用生命周期钩子函数
callHook(vm, 'beforeUpdate')
}
}
}
// 生成一个渲染 watcher 每次页面依赖的数据更新后会调用 updateComponent 进行渲染
new Watcher(
vm,
updateComponent,
noop,
watcherOptions,
true
)
// 没有老的 vnode,说明是首次渲染
if (vm.$vnode == null) {
vm._isMounted = true
// 渲染真实 dom 结束后调用 mounted 生命周期
callHook(vm, 'mounted')
}
return vm
}
Aquí está la preparación para el montaje 真实dom
, se crea el observador de representación y se llama al método updateComponent dentro del observador de representación.
Recopilación de dependencias (solo tenga una comprensión general del proceso):
- Se creará una instancia de representación antes del montaje
watcher
, y el métodowatcher
se ejecutará al ingresar al constructorthis.get()
- Luego se ejecutará
pushTarget(this)
, que es asignarDep.target
el valor a la representación actualwatcher
y empujarlo a la pila (para recuperación) - Luego ejecute
this.getter.call(vm, vm)
, es decir, laupdateComponent()
función anterior, que se ejecutavm._update(vm._render(), hydrating)
- Luego, la ejecución
vm._render()
generará la representaciónvnode
, a la que se accederá durante este procesovm 上的数据
, y se activará el objeto de datos.getter
- Hay un getter para cada valor de objeto
dep
, y cuando se activa el getterdep.depend()
, el método se llamará y ejecutará.Dep.target.addDep(this)
- Luego, se realizarán algunos juicios aquí para garantizar que los mismos datos no se agregarán varias veces, y luego se agregarán los datos calificados
push
,subs
y esto se hace完成了依赖的收集
, pero aún no se ha ejecutado, si es un objeto, es activará递归对象
el captador de todos los subelementos, también restaurará el estado de Dep.target
4. Distribuir actualizaciones
Cuando se cambian los datos de respuesta definidos, activará Object.defineProperty
el método set para cambiar directamente los datos en la capa de datos, pero el problema es que los datos se modifican, entonces, ¿cómo actualizar la vista? En este momento dep
será útil, dep
activará notify
el método para notificar la representación Watcher
para actualizar la vista.
notificar()
Cuando se active el setter, se llamará para dep.notify()
notificar a todos los suscriptores que distribuyan actualizaciones.
// src/core/observer/dep.ts
notify(info?: DebuggerEventExtraInfo) {
const subs = this.subs.filter(s => s) as DepTarget[]
if (__DEV__ && !config.async) {
// 如果不是异步,需要排序以确保正确触发
subs.sort((a, b) => a.id - b.id)
}
// 遍历dep的所有 watcher 然后执行他们的 update
for (let i = 0, l = subs.length; i < l; i++) {
const sub = subs[i]
if (__DEV__ && info) {
sub.onTrigger &&
sub.onTrigger({
effect: subs[i],
...info
})
}
// 触发更新
sub.update()
}
}
actualizar()
Se llama cuando se envía una actualización.
// src/core/observer/watcher.ts
update() {
if (this.lazy) {
this.dirty = true
} else if (this.sync) {
this.run()
} else {
// 将 Watcher 对象添加到调度器(scheduler)队列中,以便在适当的时机执行其更新操作。
queueWatcher(this)
}
}
queueWatcher()
Esta es una cola, y también es 派发更新
un punto de optimización cuando Vue lo está haciendo. Es decir, la devolución de llamada no se activa cada vez que cambian los datos watcher
, sino que estos se watcher
agregan a una cola, se watcher
ejecutan 去重
y luego nextTick
se ejecutan en la tarea asíncrona.
Estas son las principales cosas que hacer:
- Primero use el objeto has para encontrar la identificación para asegurarse de que el mismo observador solo sea empujado una vez.
- de lo contrario, si se inserta un nuevo observador durante la ejecución del observador, vendrá aquí y luego buscará de atrás hacia adelante, encontrará la primera posición donde la identificación que se insertará es mayor que la identificación en la cola actual, y insértelo en la cola, de modo que la longitud de la cola haya cambiado.
- Finalmente, mediante la espera, se garantiza
nextTick
que sólo se llamará una vez.
// src/core/observer/scheduler.ts
export function queueWatcher(watcher: Watcher) {
// 获得 watcher 的 id
const id = watcher.id
// 判断当前 id 的 watcher,是否在观察者队列中,已经存在的话return出去
if (has[id] != null) {
return
}
if (watcher === Dep.target && watcher.noRecurse) {
return
}
has[id] = true
if (!flushing) {
// 最开始会进入这里
queue.push(watcher)
} else {
// 如果在执行 watcher 期间又有新的 watcher 插入进来就会到这里,插入新的 watcher
let i = queue.length - 1
while (i > index && queue[i].id > watcher.id) {
i--
}
queue.splice(i + 1, 0, watcher)
}
// 最开始会进入这里
if (!waiting) {
waiting = true
if (__DEV__ && !config.async) {
flushSchedulerQueue()
return
}
// 因为每次派发更新都会引起渲染,所以把所有 watcher 都放到 nextTick 里调用
nextTick(flushSchedulerQueue)
}
}
vaciarSchedulerQueue()
La función flushSchedulerQueue() generalmente se ejecuta de forma asíncrona en el siguiente bucle de eventos para garantizar el procesamiento por lotes después de que se complete la operación de actualización interna de Vue.js. Esto evita la activación prematura de actualizaciones de DOM, lo que garantiza que las actualizaciones de DOM solo se activen una vez en la misma tarea asincrónica.
Estas son las principales cosas que hacer:
- Primero clasifique la cola del observador, hay tres condiciones de clasificación, vea los comentarios.
- Luego
遍历
ejecute la cola del observador correspondientewatcher.run()
; la longitud de la cola se evaluará cada vez que se cruce, porque después de la ejecución, es probable que se agregue un nuevo observador, y luego se ejecutará nuevamente elqueueWatcher()
método anterior.
// src/core/observer/scheduler.ts
function flushSchedulerQueue() {
currentFlushTimestamp = getNow()
flushing = true
let watcher, id
// 根据 id 排序,有如下条件
// 1.组件更新需要按从父到子的顺序,因为创建过程中也是先父后子
// 2.组件内我们自己写的 watcher 优先于渲染 watcher
// 3.如果某组件在父组件的 watcher 运行期间销毁了,就跳过这个 watcher
queue.sort(sortCompareFn)
// 不要缓存队列长度,因为遍历过程中可能队列的长度发生变化
for (index = 0; index < queue.length; index++) {
watcher = queue[index]
// 执行 beforeUpdate 生命周期钩子函数
if (watcher.before) {
watcher.before()
}
id = watcher.id
has[id] = null
// 执行组件内我们自己写的 watch 的回调函数并渲染组件
watcher.run()
// 检查并停止循环更新,比如在 watcher 的过程中又重新给对象赋值了,就会进入无限循环
if (__DEV__ && has[id] != null) {
circular[id] = (circular[id] || 0) + 1
if (circular[id] > MAX_UPDATE_COUNT) {
warn('无限循环了', watcher.vm)
break
}
}
}
// 重置状态之前,先保留一份队列备份
const activatedQueue = activatedChildren.slice()
const updatedQueue = queue.slice()
resetSchedulerState()
// 调用组件激活的钩子 activated
callActivatedHooks(activatedQueue)
// 调用组件更新的钩子 updated
callUpdatedHooks(updatedQueue)
cleanupDeps()
}
actualizado()
Finalmente se puede actualizar, la actualización es familiar para todos, es la función de gancho del ciclo de vida.
// src/core/observer/scheduler.ts
function callUpdatedHooks(queue: Watcher[]) {
let i = queue.length
while (i--) {
const watcher = queue[i]
const vm = watcher.vm
if (vm && vm._watcher === watcher && vm._isMounted && !vm._isDestroyed) {
callHook(vm, 'updated')
}
}
}
En este punto, se analiza básicamente el código fuente del proceso principal de respuesta a datos de Vue2.X.A continuación, presentaré los defectos y métodos de procesamiento de Object.defineProperty.
Cinco, Object.defineProperty defectos y métodos de procesamiento
Como se puede ver en lo anterior, Object.defineProperty
los defectos al secuestrar objetos y matrices:
- Indetectable o
对象属性
._ _添加
删除
- Múltiples propiedades del objeto detector requieren
遍历
el objeto. - Es imposible detectar
数组
el cambio de elementos, y el método de matriz debe reescribirse. - No se puede detectar la modificación de la longitud de la matriz.
Para estos problemas, existen soluciones correspondientes en Vue2.X.
- Necesidad de usar
this.$set
ythis.$delete
activar la capacidad de respuesta de agregar y eliminar propiedades de objetos. - Anule 7 métodos que cambiarán la matriz original y luego
ob.dep.notify()
distribuirá manualmente las actualizaciones.
Anulando métodos de matriz
Estas son las principales cosas que hacer:
- Contiene una lista de métodos que mutan la matriz ('push', 'pop', 'shift', 'unshift', 'splice', 'sort', 'reverse').
- Luego reescriba esos métodos nativos en la matriz, primero obtenga la matriz
__ob__
, es decir, esObserver 对象
, si hay un nuevo valor, llame paraobserveArray
continuar observando el cambio del nuevo valor (es decir, cambie la matriz a través de target__proto__==arrayMethods Instance tipo), y luego llame manualmentenotify
, notifique la representaciónwatcher
y ejecuteupdate
.
// src/core/observer/array.ts
// 获取数组的原型
const arrayProto = Array.prototype
// 创建一个新对象并继承了数组原型的属性和方法,将其原型指向 Array.prototype
// 为什么要克隆一份呢?因为如果直接更改数组的原型,那么将来所有的数组都会被我改了。
export const arrayMethods = Object.create(arrayProto)
// 会改变原数组的方法列表;为什么只有7个方法呢?因为只有这7个方法改变了原数组
const methodsToPatch = [
'push',
'pop',
'shift',
'unshift',
'splice',
'sort',
'reverse'
]
// 重写数组事件
methodsToPatch.forEach(function (method) {
// 保存原本的事件
const original = arrayProto[method]
// 创建响应式对象
def(arrayMethods, method, function mutator(...args) {
// 首先 先执行原始行为,以前咋滴现在就咋滴
const result = original.apply(this, args)
// 然后 再做变更通知,如何变更的呢?
// 1.获取ob实例
const ob = this.__ob__
// 2.如果是新增元素的操作,比如push、unshift或者增加元素的splice操作
let inserted
switch (method) {
case 'push':
case 'unshift':
inserted = args
break
case 'splice':
inserted = args.slice(2)
break
}
// 3.新加入的元素需要做响应式处理
if (inserted) ob.observeArray(inserted)
// 4.让内部的dep派发更新
if (__DEV__) {
ob.dep.notify({
type: TriggerOpTypes.ARRAY_MUTATION,
target: this,
key: method
})
} else {
// 派发更新
ob.dep.notify()
}
// 返回原生数组方法的执行结果
return result
})
})
La razón por la cual los atributos agregados más tarde en Vue2.x no responden
...
const keys = Object.keys(value) //获取data中的所有属性名
for (let i = 0; i < keys.length; i++) {
const key = keys[i]
defineReactive(value, key, NO_INIITIAL_VALUE, undefined, shallow, mock)
}
...
Object.defineProperty(obj, key, {
enumerable: true,
configurable: true,
get: function reactiveGetter () {
... },
set: function reactiveSetter (newVal) {
... }
})
...
Se puede ver a partir de los parámetros que necesita encontrar de key
acuerdo con lo específico para interceptar y procesar, por lo que es necesario cumplir con una condición previa, debe saber qué hay al principio, por lo que debe atravesar cada uno y definir getter , setter, por lo que los atributos agregados más tarde no responden. Además, el documento oficial de vue también decía:keys
keys [i]
key
key
Vue no puede detectar la adición o eliminación de propiedades. Dado que Vue realiza la conversión getter/setter en la propiedad al inicializar la instancia , la propiedad debe existir en el objeto de datos para que Vue la convierta en reactiva.
Seis, respuesta de datos vue2.X para hacer un resumen
1 primero
El núcleo del principio de respuesta a datos de Vue es interceptar Object.defineProperty
la adquisición y configuración de datos.
2) Siguiente
Los datos receptivos de Vue se dividen en dos categorías: 对象
y 数组
.
2.1) Objetos
getter
Itera a través de todas las propiedades del objeto y establece la suma para cada propiedad setter
, de modo que se pueda obtener y establecer en el futuro. Si el valor de la propiedad también es un objeto, se llamará varias veces para establecer observe() 递归遍历
la suma para cada clave en el valor de la propiedad .getter
setter
Al buscar datos: dep
agregue relevante en watcher
.
Al configurar datos: luego dep
notifique a los relevantes watcher
para actualizar.
2.2) Matrices
Vuelva a escribir esos métodos nativos en el arreglo, primero obtenga el arreglo (la lista de 7 métodos que cambian los elementos del arreglo), es decir, __ob__
su Observer
objeto, si hay un nuevo valor, llame para observeArray
seguir observando el cambio del nuevo valor (Eso es para target.__proto__= arrayMethods
cambiar el tipo de la instancia de la matriz a través de), y luego llamar manualmente notify
, notificar la representación watcher
y ejecutar update
.
Al agregar nuevos datos: es necesario realizar 数据响应式
el procesamiento y luego llamar a ob.dep.notify()
Notificar watcher
para actualizar
Al eliminar datos: también actualizar mediante ob.dep.notify()
Notificarwatcher
3) El papel de dep
Al igual que el ama de llaves, cuando los datos cambian, el dep llamará notify()
al método y luego notificará al observador que actualice y ejecute update
.
El papel que desempeña Dep en la fórmula de respuesta a datos es 数据的依赖收集
sum 变更通知
.
Sepa de quiénes son los observadores de los que depende (Dep) cuando obtiene los datos y notifique a los observadores de los que depende (Dep) que ejecuten su actualización (de observador) cuando cambien los datos.
4) Resumen del proceso de respuesta a datos de Vue2.X
- Compilación de plantillas
Vue primero compilará la plantilla y<template></template>
la convertirá enrender
una función de representación.- Crear instancias de componentes e inicializar datos
Durante el proceso de inicialización de los componentes de Vue, se inicializarán los datos. Vue obtendrá el objeto遍历
de la instancia del componentedata
y usaráObject.defineProperty()
el método para convertir el atributo de datos agetter
ysetter
, dándose cuenta de la capacidad de respuesta.- Secuestro de datos
Vue utiliza el método de [secuestro de datos] para realizar la observación de datos.getter
Específicamente, Vue define y en cada propiedad del objeto de datossetter
, y cuando se accede o modifica la propiedad, se activan los métodosgetter
y correspondientes.setter
- Recopilación de dependencias
Al realizar el secuestro de datos, Vue creará unWatcher
objeto y establecerá su relación de dependencia con el componente actual. Al acceder al método de datos receptivos , Watcher se agregará al objetogetter
correspondiente a la relación de dependencia que se está procesando actualmente .Dep
El objeto Dep es responsable de administrar estas dependencias y activar el Watcher correspondiente cuando se requiere una actualización.- Ejecute la función de representación
Cuando se procesan los datos,render
se ejecutará la función de representación generada en el primer paso. La función de representación se generará de acuerdo con el estado y los datos del componente虚拟DOM树
, y luego elpatch( elem,vnode)
- Realizar actualizaciones
Cuando los datos reactivos cambien,setter
se llamará a su método.Watcher
En este momento, Vue notificará al objeto relacionado con los datos para realizarupdate
la operación de actualización.- Programación de actualizaciones
Vue utiliza调度器队列
para administrar las actualizaciones que deben realizarseWatcher
. Cuando los datos cambien, el Observador se agregará a la cola del programador. La combinación de多个
las operaciones de actualización de Watcher en一个
actualizaciones por lotes asincrónicas puede reducir el rediseño y la representación innecesarios y mejorar el rendimiento.- Activar la actualización de dependencia (Watcher)
en un momento apropiado (generalmente el siguiente tick o microtarea), se ejecutará la operación de actualización del programador. Tome los correos electrónicos de la colaWatcher
uno por uno y ejecute su método de actualización. Estos métodos de actualización se activan组件
para重新渲染
mantener la vista sincronizada con los datos.
A través del proceso anterior, Vue realiza la actualización receptiva de datos. Watcher
Cuando los datos cambien, se rastreará y actualizará lo relevante y, en última instancia, afectará 组件的渲染
los resultados.
Reponer:
1) Al principio, no podía distinguir la diferencia entre la cola del observador y la cola del programador. Pensé que era una cosa, así que registrémosla aquí.
- Las colas de observadores y las colas de programadores son conceptos diferentes en Vue.
- La cola de observadores es una estrategia utilizada por Vue cuando se actualizan los datos para administrar los observadores que deben actualizarse. Cuando los datos reactivos cambien, los observadores relacionados con los datos se agregarán a la cola de observadores, a la espera de más operaciones de actualización. La cola de observadores es responsable de administrar y programar actualizaciones para estos objetos de observadores.
- La cola del programador es una cola utilizada para programar objetos Watcher en Vue. Cuando sea necesario actualizar varios Watchers, Vue procesará las operaciones de actualización de estos Watchers en lotes a través de la cola del programador. La cola del programador puede evitar la ejecución de una gran cantidad de métodos de actualización de Watcher a la vez en el mismo ciclo de eventos, lo que mejora el rendimiento y evita redibujar y renderizar innecesariamente.
- En resumen,
观察者队列
sí为了管理观察者对象的更新顺序
, y调度器队列
sí为了批量处理 Watcher 对象的更新操作
. Son diferentes en la implementación, pero todos son para garantizar la actualización receptiva de los datos y la sincronización de las vistas.
2) Si hay varias propiedades en un componente, si cada propiedad se actualiza, ¿ingresan los datos en una cola del programador?
- En Vue, las operaciones de actualización de múltiples propiedades en un componente ingresarán a la misma cola del programador. Esta cola del programador se denomina "cola de procesamiento" o "cola de actualización asíncrona".
- Cuando los datos de una propiedad cambian, el objeto Watcher relacionado con la propiedad se agregará a la cola del programador. Este proceso continuará
不断重复
hasta que se hayan agregado a la cola todas las propiedades que deben actualizarse.- En el momento adecuado, Vue ejecutará la operación de actualización del programador, atravesará los objetos Watcher en la cola de procesamiento y ejecutará su lógica de actualización a su vez. Esto puede garantizar que las operaciones de actualización de múltiples atributos se realicen en el orden correcto y que las actualizaciones por lotes se realicen de manera eficiente.
- Por lo tanto, no importa cuántas propiedades se actualicen en un componente, todas ingresarán a la misma cola del programador y el programador administrará y activará la operación de actualización de manera unificada. Este mecanismo asegura el orden y la optimización del rendimiento de las actualizaciones de datos.
Puede consultar:
Análisis del código fuente del principio de respuesta Vue3.2 y la diferencia entre Vue2.x y
Vue2
.