Directorio de artículos de la serie
[VUE]—Principio del algoritmo diff
Directorio de artículos
prefacio
En el próximo período de tiempo, estudiaré los principios relacionados con vue en profundidad, vamos.
Primero, el tiempo de funcionamiento del reloj.
Después beforeCreate
, created
antes, será initState
, en initState
será llamado initWatch
.
function initState(vm) {
vm._watchers=[];
const opts = vm.$options;
if(opts.props) initProps(vm, opts.props);
if(opts.methods) initMethods(vm, opts.methods);
if (opts.data) {
initData(vm);
} else {
observer(vm._data={
}, true);
}
if (opts.computed) initMethods(vm, opts.computed);
if (opts.watch && opts.watch !== nativeWatch) {
// 初始化watch
initWatch(vm, opts.watch);
}
}
2. Análisis del código fuente
2.1 reloj de inicio
function initWatch (vm: Component, watch: Object) {
// 1.遍历watch
for (const key in watch) {
const handler = watch[key]
if (Array.isArray(handler)) {
for (let i = 0; i < handler.length; i++) {
createWatcher(vm, key, handler[i])
}
} else {
// 2.创建watcher
createWatcher(vm, key, handler)
}
}
}
Este código simplemente itera y procesa cada uno watch
usando createWater
.
2.2 crear Observador
createWatcher
La función recibe cuatro parámetros. Dentro de la función, en realidad elimina la función de devolución de llamada cb
y los parámetros options
del objeto pasado por el usuario, y luego llama al $watch
método de una manera convencional y pasa los parámetros eliminados.
function createWatcher (
vm: Component,
expOrFn: string | Function,
handler: any,
options?: Object
) {
//监听属性的值是一个对象,包含handler,deep,immediate
if (isPlainObject(handler)) {
options = handler
handler = handler.handler
}
//如果回调函数是一个字符串,从VM中获取
if (typeof handler === 'string') {
handler = vm[handler]
}
//expOrFn是key, options是watch的全部选项
return vm.$watch(expOrFn, handler, options)
}
- Obtener la devolución de llamada del oyente
- transferir
vm.$watch
isPlainObject
El método consiste en determinar handler
si se trata de un tipo de objeto Object
;
//获取值得原始类型字符串
const _toString = Object.prototype.toString
export function isPlainObject (obj: any): boolean {
return _toString.call(obj) === '[object Object]'
}
2.3 $reloj
Primero, juzgará si la función de devolución de llamada entrante es un objeto. Si lo es, significa que el usuario ha pasado la función de devolución de llamada del segundo parámetro y el tercer parámetro options
juntos, y luego createWatcher
se llama a la función en este momento:
Vue.prototype.$watch = function (
expOrFn: string | Function,
cb: any,
options?: Object
): Function {
const vm: Component = this
if (isPlainObject(cb)) {
return createWatcher(vm, expOrFn, cb, options)
}
options = options || {
}
options.user = true
// 每一个watch都配发一个watcher
const watcher = new Watcher(vm, expOrFn, cb, options)
// 如果immediate为true,立即执行监听回调
if (options.immediate) {
try {
cb.call(vm, watcher.value)
} catch (error) {
handleError(error, vm, `callback for immediate watcher "${
watcher.expression}"`)
}
}
return function unwatchFn () {
watcher.teardown()
}
}
vm.$watch
hizo dos cosas principales
watch
asignación para cada unowatcher
;immediate
Determinar si ejecutar la devolución de llamada del oyente inmediatamente de acuerdo con la configuración
2.4 observador
export default class Watcher {
vm: Component;
expression: string;
cb: Function;
id: number;
deep: boolean;
user: boolean;
lazy: boolean;
sync: boolean;
dirty: boolean;
active: boolean;
deps: Array<Dep>;
newDeps: Array<Dep>;
depIds: SimpleSet;
newDepIds: SimpleSet;
before: ?Function;
getter: Function;
value: any;
constructor (
vm: Component,
expOrFn: string | Function,
cb: Function,
options?: ?Object,
isRenderWatcher?: boolean
) {
this.vm = vm
if (isRenderWatcher) {
vm._watcher = this
}
vm._watchers.push(this)
// options
if (options) {
this.deep = !!options.deep
this.user = !!options.user
this.lazy = !!options.lazy
this.sync = !!options.sync
this.before = options.before
} else {
this.deep = this.user = this.lazy = this.sync = false
}
this.cb = cb
this.id = ++uid // uid for batching
this.active = true
this.dirty = this.lazy // for lazy watchers
this.deps = []
this.newDeps = []
this.depIds = new Set()
this.newDepIds = new Set()
this.expression = process.env.NODE_ENV !== 'production'
? expOrFn.toString()
: ''
// parse expression for getter
if (typeof expOrFn === 'function') {
this.getter = expOrFn
} else {
this.getter = parsePath(expOrFn)
if (!this.getter) {
this.getter = noop
process.env.NODE_ENV !== 'production' && 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()
}
...
//此处只拿出了Watch类的构造方法;如需查看完整代码还请自行查阅
}
Al crear un nuevo observador, se realizan principalmente las siguientes cosas:
- monitor de clave
- devolución de llamada del oyente (
watch
incb
) - monitor configurado
options
3. Monitoreo profundo
options
¿Cómo implementar la observación profunda cuando el atributodeep
en el parámetro de opción es ?true
La llamada observación profunda significa que seremos notificados cuando cambie el objetoobj
, y también seremos notificados cuando cambie el obj.a
atributo .
No es difícil lograr esta función, sabemos que si queremos que nos notifiquen cuando cambien los datos, solo necesitamos convertirnos en una dependencia de estos datos, porque cuando los datos cambien, todas sus dependencias serán notificadas, entonces, ¿cómo convertirse en una dependencia de datos? , muy simple, solo lea los datos. Es decir, solo necesitamos leer recursivamente todos los valores en el objeto al crear una watcher
instancia , luego la instancia se agregará a la lista de dependencia de todos los valores en el objeto, y luego cuando cualquier valor en el los cambios de objetos serán notificadosobj
watcher
.
Cuando watcher
se crea una instancia , Watcher
se ejecuta un método en la clase get
para leer los datos observados.
export default class Watcher {
constructor (/* ... */) {
// ...
this.value = this.get()
}
get () {
// ...
// "touch" every property so they are all tracked as
// dependencies for deep watching
if (this.deep) {
traverse(value)
}
return value
}
}
En un get
método, si se pasa deep===true
, la función se llamará traverse
.
3.1 atravesar
const seenObjects = new Set()
export function traverse (val: any) {
_traverse(val, seenObjects)
seenObjects.clear()
}
function _traverse (val: any, seen: SimpleSet) {
let i, keys
const isA = Array.isArray(val)
if ((!isA && !isObject(val)) || Object.isFrozen(val) || val instanceof VNode) {
return
}
if (val.__ob__) {
const depId = val.__ob__.dep.id
if (seen.has(depId)) {
return
}
seen.add(depId)
}
if (isA) {
// 如果是数组,循环遍历
i = val.length
while (i--) _traverse(val[i], seen)
} else {
// 对象
keys = Object.keys(val)
i = keys.length
// 递归遍历每一个属性
while (i--) _traverse(val[keys[i]], seen)
}
}
Se puede ver que esta función es en realidad un proceso de recorrido recursivo , que recorre recursivamente y lee los valores internos de los datos observados.
Primero juzgue el val
tipo de entrada, si no es Array或object
o ha sido congelado, luego regrese directamente y salga del programa.
Luego obténgalo val的dep.id
y guárdelo en la colección creada seen
, porque la colección tiene un efecto de deduplicación natural en comparación con los datos, para garantizar que los datos almacenados dep.id
no se dupliquen y no causen dependencias de colección duplicadas.
A continuación, se juzga que si es una matriz, haga un bucle en la matriz y llame recursivamente a cada elemento de la matriz _traverse
; si es un objeto, elimine todos los objetos key
, luego realice la operación de lectura y luego recurra al valor interno.
De esta forma, después de leer recursivamente todos los valores en los datos observados, la watcher
instancia se agregará a la lista de dependencias de todos los valores en el objeto, y luego se le notificará cuando cambie algún valor en el objeto.
Para resumir, de hecho, el método poligonal se entiende bien. Lo principal que se debe hacer es:
- Al leer, esta propiedad se puede recopilar
watch-watcher
; - Objetos de nivel profundo, donde cada propiedad también responde, cada propiedad tiene su propio recopilador de dependencias, al leer continuamente cada propiedad, cada propiedad se puede recopilar
watch-watcher
; de esta manera, no importa qué tan profundo cambie la propiedad en el objeto, se notificaráwatch-watcher
, así que esto completa el monitoreo profundo;
actualice