Agendando a implementação do sistema responsivo e do Watcher

Antecedentes

Considerando que o vue3 ainda não foi lançado oficialmente, e algumas das ideias de implementação no vue2 ainda são muito valiosas para referência, portanto, essa explicação de princípios ainda é vue2, espero que você tenha inspiração ~

Implementação de sistema responsivo

O Vue.js é uma estrutura MVVM. O modelo de dados é apenas objetos JavaScript comuns, mas quando esses objetos são manipulados, eles podem afetar as visualizações correspondentes. Sua principal implementação é o "sistema responsivo".

No vue 2.0, ele se baseia na  Object.definePropertyimplementação do "sistema responsivo". Vue3 é baseado na  Proxy/Reflect realização, análise detalhada do vue3 terá tempo para escrever, este artigo é sobre a realização do vue2.

Envolve principalmente atributos:

  • enumerável, se a propriedade pode ser enumerada, o padrão é false.

  • configurável, se a propriedade pode ser modificada ou excluída, o padrão é false.

  • get, o método para obter o atributo

  • set, o método de configuração de propriedades.

O princípio básico da capacidade de resposta é processar os dados das opções no construtor Vue. Ou seja, quando a instância do vue é inicializada, cada propriedade de dados, objetos e outros objetos é definida uma vez por Object.defineProperty.Quando os dados são configurados, execute algumas operações para alterar a visualização correspondente.

class Vue {
    /* Vue构造类 */
    constructor(options) {
        this._data = options.data;
        observer(this._data);
    }
}
function observer (value) {
    if (!value || (typeof value !== 'object')) {
        return;
    }
    
    Object.keys(value).forEach((key) => {
        defineReactive(value, key, value[key]);
    });
}
function defineReactive (obj, key, val) {
    Object.defineProperty(obj, key, {
        enumerable: true,       /* 属性可枚举 */
        configurable: true,     /* 属性可被修改或删除 */
        get: function reactiveGetter () {
            return val;
        },
        set: function reactiveSetter (newVal) {
            if (newVal === val) return;
            cb(newVal);
        }
    });
}

Em aplicações práticas, vários sistemas são extremamente complicados. Suponha que agora tenhamos um objeto global, podemos usá-lo em vários objetos do Vue para exibição. Ou talvez os dados gravados nos dados não sejam aplicados à visualização e a atualização da visualização no momento seja supérflua. Isso depende do processo de coleta.

Coleção dependente

A chamada coleta dependente é coletar o local onde um dado é usado.Quando esses dados mudam, ele notifica cada local para realizar a operação correspondente. O modo básico de "assinante" no VUE é o seguinte:

exportdefaultclass Dep {
  static target: ?Watcher;
  id: number;
  subs: Array<Watcher>;

  constructor () {
    this.id = uid++
    this.subs = []
  }

  addSub (sub: Watcher) {
    this.subs.push(sub)
  }

  removeSub (sub: Watcher) {
    remove(this.subs, 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()
    }
  }
}

Com os assinantes, vamos dar uma olhada na implementação do Watcher. O código-fonte Watcher é mais lógico e o modelo simplificado é o seguinte

class Watcher{
    constructor(vm,expOrFn,cb,options){
        //传进来的对象 例如Vue
        this.vm = vm
        //在Vue中cb是更新视图的核心,调用diff并更新视图的过程
        this.cb = cb
        //收集Deps,用于移除监听
        this.newDeps = []
        this.getter = expOrFn
        //设置Dep.target的值,依赖收集时的watcher对象
        this.value =this.get()
    }

    get(){
        //设置Dep.target值,用以依赖收集
        pushTarget(this)
        const vm = this.vm
        let value = this.getter.call(vm, vm)
        return value
    }

    //添加依赖
      addDep (dep) {
          // 这里简单处理,在Vue中做了重复筛选,即依赖只收集一次,不重复收集依赖
        this.newDeps.push(dep)
        dep.addSub(this)
      }

      //更新
      update () {
        this.run()
    }

    //更新视图
    run(){
        //这里只做简单的console.log 处理,在Vue中会调用diff过程从而更新视图
        console.log(`这里会去执行Vue的diff相关方法,进而更新数据`)
    }
}

defineReactive detalhada lógica

exportfunction defineReactive (
  obj: Object,
  key: string,
  val: any,
  customSetter?: ?Function,
  shallow?: boolean
) {
  const dep = new Dep()

  const property = Object.getOwnPropertyDescriptor(obj, key)
  if (property && property.configurable === false) {
    return
  }

  // cater for pre-defined getter/setters
  const getter = property && property.get
  const setter = property && property.set
  if ((!getter || setter) && arguments.length === 2) {
    val = obj[key]
  }

  let childOb = !shallow && 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
      /* eslint-disable no-self-compare */
      if (newVal === value || (newVal !== newVal && value !== value)) {
        return
      }
      /* eslint-enable no-self-compare */
      if (process.env.NODE_ENV !== 'production' && customSetter) {
        customSetter()
      }
      if (setter) {
        setter.call(obj, newVal)
      } else {
        val = newVal
      }
      childOb = !shallow && observe(newVal)
      dep.notify()
    }
  })
}

所以响应式原理就是,我们通过递归遍历,把vue实例中data里面定义的数据,用defineReactive(Object.defineProperty)重新定义。每个数据内新建一个Dep实例,闭包中包含了这个 Dep 类的实例,用来收集 Watcher 对象。在对象被「读」的时候,会触发 reactiveGetter 函数把当前的 Watcher 对象(存放在 Dep.target 中)收集到 Dep 类中去。之后如果当该对象被「写」的时候,则会触发 reactiveSetter 方法,通知 Dep 类调用 notify 来触发所有 Watcher 对象的 update 方法更新对应视图。

O nascimento de Watcher

No Vue, existem 4 situações que irão gerar o Watcher:

  1. O inspetor no objeto de instância do Vue, observa os dados raiz e renderiza novamente o componente quando houver uma alteração updateComponent = () => {vm._update (vm._render (), hydrating)} vm._watcher = new Watcher (vm, updateComponent, noop)

  2. O inspetor criado pelo usuário com o atributo watch no objeto vue

  3. O atributo calculado criado pelo usuário no objeto vue é essencialmente um observador

  4. Observador criado pelo usuário usando vm. $ Watch

O Wathcer aumentará ou diminuirá e pode ser adicionado ao renderizar. Portanto, deve haver um agendamento para agendar o Observador. Alguns códigos principais são os seguintes:

 queue.sort((a, b) => a.id - b.id)

  // do not cache length because more watchers might be pushed
  // as we run existing watchers
  for (index = 0; index < queue.length; index++) {
    watcher = queue[index]
    if (watcher.before) {
      watcher.before()
    }
    id = watcher.id
    has[id] = null
    watcher.run()
    // in dev build, check and stop circular updates.
    if (process.env.NODE_ENV !== 'production' && has[id] != null) {
      circular[id] = (circular[id] || 0) + 1
      if (circular[id] > MAX_UPDATE_COUNT) {
        warn(
          'You may have an infinite update loop ' + (
            watcher.user
              ? `in watcher with expression "${watcher.expression}"`
              : `in a component render function.`
          ),
          watcher.vm
        )
        break
      }
    }
  }

O papel do Cronograma:

  1. Desduplicação, cada Observador possui um ID exclusivo. Primeiro, se o ID já estiver na fila, pule-o, não há necessidade de repetir a execução.Se o ID não estiver na fila, depende se a fila está sendo executada. Se não estiver em execução, a fila é executada no próximo intervalo de tempo, portanto, a fila é sempre executada de forma assíncrona.

  2. A classificação é realizada na ordem de análise e renderização, ou seja, o Watcher é executado primeiro. O ID no Watcher aumenta automaticamente, o ID criado primeiro é menor que o ID criado posteriormente. Portanto, haverá as seguintes regras:

2.1 Os componentes podem ser aninhados e a análise deve analisar primeiro o componente pai e depois o componente filho. Portanto, o ID do componente pai é menor que o componente filho.

2.2 O Watcher criado pelo usuário será analisado antes do criado durante a renderização. Portanto, o ID do Watcher criado pelo usuário é menor que o criado durante a renderização.

  1. Excluir observador Se o observador de um componente estiver na fila e seu componente pai for excluído, esse observador também deverá ser excluído neste momento.

  2. Durante a execução da fila, é armazenada uma circular de objeto, que contém o número de execuções de cada observador. Se algum observador executar mais do que o número de vezes definido por MAX_UPDATE_COUNT, ele será considerado um loop infinito e não será mais executado. O padrão é 100 vezes.

总之,调用的作用就是管理 Watcher。

Suplemento

Como usar Object.defineProperty para redefinir o objeto de matriz no VUE2? Por que a exibição não muda responsivamente quando modificamos diretamente um item nos dados (arr [3] = 4)

A resposta é que a capacidade de resposta da matriz não está completa e o VUE reescreveu apenas métodos limitados. A lógica de reescrita é a seguinte:

const arrayProto = Array.prototype
exportconst arrayMethods = Object.create(arrayProto)

const methodsToPatch = [
  'push',
  'pop',
  'shift',
  'unshift',
  'splice',
  'sort',
  'reverse'
]

/**
 * Intercept mutating methods and emit events
 */
methodsToPatch.forEach(function (method) {
  // cache original method
  const original = arrayProto[method]
  def(arrayMethods, method, function mutator (...args) {
    const result = original.apply(this, args)
    const ob = this.__ob__
    let inserted
    switch (method) {
      case'push':
      case'unshift':
        inserted = args
        break
      case'splice':
        inserted = args.slice(2)
        break
    }
    if (inserted) ob.observeArray(inserted)
    // notify change
    ob.dep.notify()
    return result
  })
})
Publicado 117 artigos originais · 69 elogios · 10.000+ visualizações

Acho que você gosta

Origin blog.csdn.net/zsd0819qwq/article/details/105356741
Recomendado
Clasificación