Análise de código-fonte Vue: o princípio da vinculação de dados bidirecional (responsivo)

Qual é o princípio da vinculação de dados bidirecional (responsivo) de vue? Isso ainda tem que começar com o código-fonte da vue:

Sabemos que uma das principais características do vue é 数据驱动视图como entender as seis palavras da visão baseada em dados.

Dados: estado;

Drive: função de renderização;

Visualização: UI da página;

Dessa forma, de fato, podemos obter a fórmula: UI = render (state), a mudança de estado leva diretamente à mudança da UI, e a constante é render, e vue desempenha o papel de render. Então, como vue sabe a mudança de estado? Existe uma palavra chamada 变化侦测, porque é mencionada nas pilhas de tecnologia mainstream atuais, vue, react e angular, ou seja 状态追宗, uma vez que os dados mudam, atualiza a visão. Vamos começar com a detecção de alteração do objeto.

A chamada detecção de mudança significa que podemos saber quando os dados foram lidos ou quando os dados foram reescritos.

Detecção de dados

1. Realize a detecção de objetos (observável)

A detecção de objetos é feita por Object.defineProperty()este método.

Primeiro defina um objeto animal:


let animal = {
    name: 'dog',
    age: 3

Então, como sabemos quando as propriedades desse objeto animal são modificadas? Vejamos as seguintes operações:


let name = 'cat'
Object.defineProperty(animal, 'name', {
    configrable: true,    //    描述属性是否配置,以及可否删除
    enumerable: true,     //    描述属性是否会出现在for in 或者 Object.keys()的遍历中
    writable: true,       //    属性的值是否可以被重写
    get () {
        //    这里读取了name的值
        return name
    },
    set (value) {
        //    这里设置了name的值
        name = value
    }

Dessa forma, quando o valor de nome for alterado para cat, ele se tornará observável e detectável. Mas isso só é possível para um determinado atributo do objeto ser detectado, mas o atributo do objeto geralmente não é 1. O que devo fazer quando houver mais de um atributo? Na verdade, é muito simples, sim, sim, é sobre a recursão que você quer falar. Diretamente no código:

//    源码位置:src/core/observer/index.js
export class Observer {
    constructor (value) {
        this.value = value
        def(value,'__ob__',this)
        if (Array.isArray(value)) {
              // 当value为数组时的逻辑
        } else {
              this.walk(value)
        }
    }
 
    walk (obj: Object) {
        const keys = Object.keys(obj)
        for (let i = 0; i < keys.length; i++) {
              defineReactive(obj, keys[i])
        }
    }
}
function defineReactive (obj,key,val) {
      if (arguments.length === 2) {
            val = obj[key]
      }
      if(typeof val === 'object'){
          new Observer(val)
      }
      Object.defineProperty(obj, key, {
          enumerable: true,
          configurable: true,
          get(){
              console.log(`${key}属性被读取了`);
              return val;
          },
          set(newVal){
              if(val === newVal){
                  return
              }
              console.log(`${key}属性被修改了`);
              val = newVal;
          }
      })
}

Declaramos uma classe Observer, que é usada para transformar todos os atributos do objeto em detectáveis, e precisamos adicionar uma instância de Observer com o atributo __ob__ e o valor para cada objeto detectável, que é equivalente Yu definiu um sinalizador para cada valor, o que significa que o valor é responsivo. Então, quando o valor é um objeto, obtemos todas as suas chaves, percorremos o método de chamada Object.defineProperty (), chamamos o método get / set para detectar, quando é descoberto que o objeto de entrada é um Objeto, passamos um forma recursiva, o Object é instanciado pela classe Observer novamente. Desta forma, alcançamos a detecção de objetos.

2. Realize a detecção de matriz (observável)

Falamos sobre detecção de objeto acima, agora vamos falar sobre detecção de array.

O observável da matriz é diferente do observável do objeto, porque a matriz não pode ser observada com Object.defineProperty (). Então, como tornar o array observável, de fato, o mesmo, o array é 拦截器feito, então o que é o interceptor sagrado?

Sabemos que existem apenas alguns métodos nativos para manipular matrizes em JavaScript, push / pop / shift / unshift / splice / sort / reverse. Todos esses métodos são acessados ​​por meio de Array.prototype, portanto, se redefinirmos um método em Array.prototype para implementar um método nativo, como newPush para obter o mesmo efeito de push, é perfeito? O método nativo não é modificado e a operação correspondente pode ser realizada antes de chamar o método nativo.

let arr = [1,2,3]
//    arr.push(4) 原生方法调用
Array.prototype.newPush = function(val){
      console.log('arr被修改了')
      this.push(val)
}
arr.newPush(4)

Então数组的拦截器就是在数组实例和Array.prototype之间重写了操作数组的方法 ,. Desta forma, quando o método processado é chamado, o método reescrito é chamado em vez do método nativo. Observe a implementação do interceptor:

// 源码位置:/src/core/observer/array.js
const arrayProto = Array.prototype
export const arrayMethods = Object.create(arrayProto)
const methodsToPatch = [
  'push',
  'pop',
  'shift',
  'unshift',
  'splice',
  'sort',
  'reverse'
]
methodsToPatch.forEach(function (method) {
  const original = arrayProto[method]
  Object.defineProperty(arrayMethods, method, {
    enumerable: false,
    configurable: true,
    writable: true,
    value:function mutator(...args){
      const result = original.apply(this, args)
      return result
    }
  })
})

Esta é a implementação do interceptor. Quando o método do array é chamado, o que é realmente chamado é mutator函数, portanto, podemos fazer algumas operações nesta função. Mas isso não é suficiente 只有将拦截器挂载在数组实例和Array.prototype之间才能生效,也就是将数据的__proto__属性设置为拦截器arrayMethods即可.

augment (valor arrayMethods,, arrayKeys)

// 源码位置:/src/core/observer/index.js
export class Observer {
  constructor (value) {
    this.value = value
    if (Array.isArray(value)) {
      const augment = hasProto
        ? protoAugment
        : copyAugment
      augment(value, arrayMethods, arrayKeys)
    } else {
      this.walk(value)
    }
  }
}
// 能力检测:判断__proto__是否可用,因为有的浏览器不支持该属性
export const hasProto = '__proto__' in {}
 
const arrayKeys = Object.getOwnPropertyNames(arrayMethods)
 
/**
 * Augment an target Object or Array by intercepting
 * the prototype chain using __proto__
 */
function protoAugment (target, src: Object, keys: any) {
  target.__proto__ = src
}
 
/**
 * Augment an target Object or Array by defining
 * hidden properties.
 */
/* istanbul ignore next */
function copyAugment (target: Object, src: Object, keys: Array<string>) {
  for (let i = 0, l = keys.length; i < l; i++) {
    const key = keys[i]
    def(target, key, src[key])
  }
}
 

Quando o interceptor entra em vigor, se o array muda, ele pode ser detectado.

A descrição acima é como fazer alterações em objetos e arrays detectáveis. Está longe de ser o suficiente para apenas ser detectado. Vamos falar sobre como confiar na coleção.

Coleta de dados dependente

1. Coleção dependente de objetos

Coleta dependente : quais dados são usados ​​na visão, a visão depende de quais dados e os dados alterados são coletados.Este processo é uma coleta dependente.

Onde coletar dependências : os dados observáveis gettersão obtidos em, portanto, getter é um lugar para coletar dependências.

Quando notificar atualizações dependentes : alterações de dados setterpodem ser observadas nas, naturalmente, atualizações dependentes de notificação estão no setter.

Onde as dependências devem ser coletadas? E olhe para baixo:

Como não podemos controlar o tamanho dos dados atualizados, precisamos de um gerenciador de dependências para a coleção de dependências:

// 源码位置:src/core/observer/dep.js
export default class Dep {
   constructor () {
       this.subs = []
   }

   addSub (sub) {
       this.subs.push(sub)
   }
   // 删除一个依赖
   removeSub (sub) {
       remove(this.subs, sub)
   }
   // 添加一个依赖
   depend () {
       if (window.target) {
           this.addSub(window.target)
       }
   }
   // 通知所有依赖更新
   notify () {
       const subs = this.subs.slice()
       for (let i = 0, l = subs.length; i < l; i++) {
           subs[i].update()
       }
   }
}

/**
* Remove an item from an array
*/
export function remove (arr, item) {
   if (arr.length) {
       const index = arr.indexOf(item)
       if (index > -1) {
           return arr.splice(index, 1)
       }
   }
}

Com o gerenciador de dependências, podemos coletar dependências no getter e notificar a atualização da dependência no setter.

function defineReactive (obj,key,val) {
    if (arguments.length === 2) {
         val = obj[key]
    }
    if(typeof val === 'object'){
        new Observer(val)
    }
    const dep = new Dep()  //实例化一个依赖管理器,生成一个依赖管理数组dep
    Object.defineProperty(obj, key, {
        enumerable: true,
        configurable: true,
        get(){
            dep.depend()    // 在getter中收集依赖
            return val;
        },
        set(newVal){
            if(val === newVal){
            return
        }
        val = newVal;
        dep.notify()   // 在setter中通知依赖更新
     }
  })
}

O método dep.depend () é chamado no getter para coletar dependências e o método dep.notify () é chamado no setter para notificar todas as atualizações de dependência.

Na verdade, uma classe de inspetor que depende de dados para cada mudança é implementada no Vue. Quando os dados são alterados posteriormente, não notificamos diretamente a atualização dependente, mas notificamos a instância de Watch dependente, e a instância de Watcher notificará a visão real.

export default class Watcher {
  constructor (vm,expOrFn,cb) {
    this.vm = vm;
    this.cb = cb;
    this.getter = parsePath(expOrFn)
    this.value = this.get()
  }
  get () {
    window.target = this;
    const vm = this.vm
    let value = this.getter.call(vm, vm)
    window.target = undefined;
    return value
  }
  update () {
    const oldValue = this.value
    this.value = this.get()
    this.cb.call(this.vm, this.value, oldValue)
  }
}
 
/**
 * Parse simple path.
 * 把一个形如'data.a.b.c'的字符串路径所表示的值,从真实的data对象中取出来
 * 例如:
 * data = {a:{b:{c:2}}}
 * parsePath('a.b.c')(data)  // 2
 */
const bailRE = /[^\w.$]/
export function parsePath (path) {
  if (bailRE.test(path)) {
    return
  }
  const segments = path.split('.')
  return function (obj) {
    for (let i = 0; i < segments.length; i++) {
      if (!obj) return
      obj = obj[segments[i]]
    }
    return obj
  }
}
 

análise:

  • Quando o relógio é instanciado, o construtor é executado primeiro;
  • Chame o método de instância this.get () no construtor;
  • No método get (), primeiro atribua a própria instância a um objeto único global window.target por window.target = this e, em seguida, obtenha o objeto dependente por let value = this.getter.call (vm, vm) Data, o a finalidade de obter dados dependentes é acionar o getter nos dados. Como dissemos acima, dep.depend () é chamado para coletar dependências no getter e window.target é recuperado de dep.depend (). E armazena-o em a matriz de dependência e libere window.target no final do método get ().
  • Quando os dados mudam, o setter de dados é acionado. O método dep.notify () é chamado no setter. No método dep.notify (), todas as dependências (isto é, instâncias do observador) são percorridas e o método dependente update () é Ou seja, o método de instância update () na classe Watcher chama a função de retorno de chamada update da mudança de dados no método update () para atualizar a visualização.

Um breve resumo é:

O inspetor primeiro define a si mesmo para o local designado globalmente exclusivo (window.target) e, em seguida, lê os dados. Como os dados são lidos, o getter desses dados será acionado. Então, no getter, o Watcher que está lendo os dados será lido da posição globalmente única e este watcher será coletado no Dep. Após a coleta, quando os dados forem alterados, uma notificação será enviada a cada Vigia do Dep. Dessa forma, o Watcher pode se inscrever ativamente em qualquer alteração de dados. Para facilitar o entendimento, desenhamos o fluxograma de relacionamento, conforme mostrado a seguir:

Insira a descrição da imagem aqui
Acima, todas as operações, como detecção de dados de objeto, coleta de dependências e atualização de dependências, estão completamente concluídas.

2. Coleção de dependência de array

Para arrays, na verdade, existe um método semelhante aos objetos na coleção dependente.

A coleção da dependência do array está na classe observador:

// 源码位置:/src/core/observer/index.js
export class Observer {
  constructor (value) {
    this.value = value
    this.dep = new Dep()    // 实例化一个依赖管理器,用来收集数组依赖
    if (Array.isArray(value)) {
      const augment = hasProto
        ? protoAugment
        : copyAugment
      augment(value, arrayMethods, arrayKeys)
    } else {
      this.walk(value)
    }
  }
}

Dessa forma, também implementamos um gerenciador de dependências no observador.

Portanto, podemos ver como as dependências da matriz são coletadas e ir diretamente para o código:

function defineReactive (obj,key,val) {
  let childOb = observe(val)
  Object.defineProperty(obj, key, {
    enumerable: true,
    configurable: true,
    get(){
      if (childOb) {
        childOb.dep.depend()
      }
      return val;
    },
    set(newVal){
      if(val === newVal){
        return
      }
      val = newVal;
      dep.notify()   // 在setter中通知依赖更新
    }
  })
}
 
/**
 * Attempt to create an observer instance for a value,
 * returns the new observer if successfully observed,
 * or the existing observer if the value already has one.
 * 尝试为value创建一个0bserver实例,如果创建成功,直接返回新创建的Observer实例。
 * 如果 Value 已经存在一个Observer实例,则直接返回它
 */
export function observe (value, asRootData){
  if (!isObject(value) || value instanceof VNode) {
    return
  }
  let ob
  if (hasOwn(value, '__ob__') && value.__ob__ instanceof Observer) {
    ob = value.__ob__
  } else {
    ob = new Observer(value)
  }
  return ob
}

Dentro da função observar, primeiro determine se há um atributo __ob__ nos dados de entrada atuais, porque conforme mencionado no artigo anterior, se os dados tiverem um atributo __ob__, significa que ele foi transformado em um tipo reativo. significa que os dados ainda não estão responsivos, então chame o novo Observer (valor) para convertê-lo em responsivo e retorne a instância do Observer correspondente aos dados. Na função defineReactive, primeiro obtenha a instância do Observer childOb correspondente aos dados e, em seguida, chame o gerenciador de dependência na instância do Observer no getter para coletar as dependências.

Isso torna muito mais fácil contar com notificações para contar com atualizações.

methodsToPatch.forEach(function (method) {
 const original = arrayProto[method]
 def(arrayMethods, method, function mutator (...args) {
   const result = original.apply(this, args)
   const ob = this.__ob__
   // notify change
   ob.dep.notify()
   return result
 })
})

Neste ponto, a coleção de dependências do array é basicamente alcançada, mas isso ainda não acabou. A matriz não é uma camada, talvez várias camadas, portanto, uma inspeção profunda é necessária:

export class Observer {
  value: any;
  dep: Dep;
 
  constructor (value: any) {
    this.value = value
    this.dep = new Dep()
    def(value, '__ob__', this)
    if (Array.isArray(value)) {
      const augment = hasProto
        ? protoAugment
        : copyAugment
      augment(value, arrayMethods, arrayKeys)
      this.observeArray(value)   // 将数组中的所有元素都转化为可被侦测的响应式
    } else {
      this.walk(value)
    }
  }
 
  /**
   * Observe a list of Array items.
   */
  observeArray (items: Array<any>) {
    for (let i = 0, l = items.length; i < l; i++) {
      observe(items[i])
    }
  }
}
 
export function observe (value, asRootData){
  if (!isObject(value) || value instanceof VNode) {
    return
  }
  let ob
  if (hasOwn(value, '__ob__') && value.__ob__ instanceof Observer) {
    ob = value.__ob__
  } else {
    ob = new Observer(value)
  }
  return ob
}

Detecção de novos elementos na matriz

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   // 如果是push或unshift方法,那么传入参数就是新增的元素
        break
      case 'splice':
        inserted = args.slice(2) // 如果是splice方法,那么传入参数列表中下标为2的就是新增的元素
        break
    }
    if (inserted) ob.observeArray(inserted) // 调用observe函数将新增的元素转化成响应式
    // notify change
    ob.dep.notify()
    return result
  })
})

O princípio geral de resposta dos dados vue é assim. Os objetos não podem ser monitorados, as propriedades dos objetos são aumentadas ou diminuídas e os arrays não podem ser monitorados quanto a mudanças nos subscritos dos arrays. Métodos Vue.set () e Vue.delete foram adicionados ao vue para lidar com eles.

O acima é todo o princípio reativo, ou o princípio da ligação de dados bidirecional.

Apenas como um registro de aprendizagem, consulte o artigo: https://blog.csdn.net/leelxp/article/details/106936827

Acho que você gosta

Origin blog.csdn.net/weixin_44433499/article/details/114259116
Recomendado
Clasificación