Я хочу знать, часы с белыми словами и вычисляются

Vue.js
Я хочу знать, часы с белыми словами и вычисляются

задний план

Меня всегда интересовал vuewatchа такжеcomputedполупоняты, немного знают (например:watchа такжеcomputedСуть в томnew Watcher,computedКэш есть, он будет выполняться только при вызове, и он будет запускаться снова только при изменении зависимых данных...), а дальше такого нет.

Я также читал много статей, написанных большими парнями, там указан большой кусок исходного кода, который действительно заставляет меня, новичка, смотреть на него, и, естественно, я не хочу его читать. Недавно я снова начал изучать исходный код vue, и тогда я действительно понял принцип их реализации.

data() {
  return {
    msg: 'hello guys',
    info: {age:'18'},
    name: 'FinGet'
  }
}

watcher

watcherчто это такое? слушатель? это классclass!

class Watcher{
  constructor(vm,exprOrFn,callback,options,isRenderWatcher){
  }
}
  • vmvue-экземпляр
  • exprOrFnЭто может быть строка или функция обратного вызова (оглянитесь назад, если вы немного запутались, сейчас это не имеет значения)
  • optionsРазличные элементы конфигурации (что настраивать, оглянуться назад)
  • isRenderWatcherтак или иначеоказыватьWathcer

initState

Инициализация Vue выполнитinitStateметоды, из которых наиболее известныinitData,то естьObject.definePropertyПерехват данных.

export function initState(vm) {
  const opts = vm.$options;
  // vue 的数据来源 属性 方法 数据 计算属性 watch
  if(opts.props) {
    initProps(vm);
  }
  if(opts.methods) {
    initMethod(vm);
  }
  if(opts.data) {
    initData(vm);
  }
  if(opts.computed){
    initComputed(vm);
  }
  if(opts.watch) {
    initWatch(vm, opts.watch);
  }
}

В захвате данных,Watcherхороший другDepПоявился,Depпросто поставитьWatcherспасти.

function defineReactive(data, key, val) {
  let dep = new Dep(); 
  Object.defineProperty(data, key, {
    get(){
      if(Dep.target) {
        dep.depend(); // 收集依赖
      }
      return val;
    },
    set(newVal) {
      if(newVal === val) return;
      val = newVal;
      dep.notify(); // 通知执行
    }
  })
}

когдаinitDataкогда,Dep.targetНичего, так я собрал одиночество.targetпривязан кDepВ этом классе, а не в экземпляре.

но когда$mountПосле этого все было иначе. Что касается$mountчто выполняется вcompile,generate,render,patch,diffЭто не то, чему посвящена эта статья, это не важно, обходите это стороной!

Вам нужно знать только одно: будет выполнен следующий код

new Watcher(vm, updateComponent, () => {}, {}, true); // true 表示他是一个渲染watcher

updateComponentПросто обновите, независимо от конкретной реализации, теперь это страница, которая будет обновлятьсяПерезвоните, он будет присутствоватьWatcherизgetterсередина. Он соответствует первомуexprOrFnпараметр.

Эй, на этот раз все по-другому:

  1. Отрисовка страницы — это вызов данных, которые вы определили (не суетитесь, это определено, а не вызывается), и оно пойдет.get.
  2. надnew WatcherПри вызове метода для помещения этого экземпляра вDep.targetвверх, иди сноваgetВы можете собирать зависимости в
pushTarget(watcher) {
  Dep.target = watcher;
}

Эти две вещи просто собираются вместе, затемdep.depend()Просто работай.

Так что тут можно понять одно, всеdataДанные, определенные в , будут собирать рендер всякий раз, когда он вызывается.watcher, то есть изменения данных, выполнитьsetсерединаdep.notifyбудет оказыватьwatcher

На следующем рисунке показано определениеmsg,info,nameтри данных, все они имеютоказыватьWatcher:

Зоркие друзья должны были видетьmsgЕсть дваwatcher, один определяется пользователемwatch, другой также определяется пользователемwatch. Ах, конечно нет,vueЭто сделано для дедупликации, повторов не будетwatcher, как и следовало ожидать, другойcomputed watcher;

пользовательские часы

Обычно мы используем такие часы:

watch: {
  msg(newVal, oldVal){
    console.log('my watch',newVal, oldVal)
  }
  // or
  msg: {
    handler(newVal, oldVal) {
      console.log('my watch',newVal, oldVal)
    },
    immediate: true
  }
}

Здесь будет выполнятьсяinitWatch, после одной операции извлекаетсяexprOrFn(На этот раз это строка (msg)),handler,options, что то же самое, чтоWatcherОн подходит необъяснимо, а затем называет это логически.vm.$watchметод.

 Vue.prototype.$watch = function(exprOrFn, cb, options = {}) {
    options.user = true; // 标记为用户watcher
    // 核心就是创建个watcher
    const watcher = new Watcher(this, exprOrFn, cb, options);
    if(options.immediate){
      cb.call(vm,watcher.value)
    }
 }

Да ладно, я не могу не посмотреть на этот код (я изначально вставил длинный абзац, но сказал на просторечии, поэтому удалил те, которые имеют мало отношения к этому абзацу):

class Watcher{
  constructor(vm,exprOrFn,callback,options,isRenderWatcher){
    this.vm = vm;
    this.callback = callback;
    this.options = options;
    if(options) {
      this.user = !!options.user;
    }
    this.id = id ++;
    if (typeof exprOrFn == 'function') {
      this.getter = exprOrFn; // 将传过来的回调函数 放到getter属性上
    } else {
      // 当exprOrFn 是个字符串的时候,就需要去取值(返回一个取值函数 闭包)
      this.getter = parsePath(exprOrFn);
      // 如果getter不存在 会赋值一个 函数
      if (!this.getter) {
        this.getter = (() => {});
      }
    }
    this.value = this.get();
  }
  get(){
    pushTarget(this); // 把当前watcher 存入dep中
    let result = this.getter.call(this.vm, this.vm); // 渲染watcher的执行 这里会走到observe的get方法,然后存下这个watcher
    popTarget(); // 再置空 当执行到这一步的时候 所以的依赖收集都完成了,都是同一个watcher
    return result;
  }
}
// 这个就是拿来把msg的值取到,取到的就是oldVal
function parsePath(path) {
  if (!path) {
    return
  }
  var segments = path.split('.');
  return function(obj) {
    for (var i = 0; i < segments.length; i++) {
      if (!obj) { return }
      obj = obj[segments[i]];
    }
    return obj
  }
}

Как вы видете,new Watcherбудет выполнятьgetметод, когдаРендер-наблюдательОтобразит страницу, выполнитupdateComponent, когда он являетсяНаблюдатель за пользователямизаключается в выполненииparsePathметод в методе возврата, а затем получить значениеthis.valueто естьoldVal.

Эй, эй, теперь, когда значение принято, оно снова исчезлоmsgизgetвнутри, на этот разdep.depend()снова работает,Наблюдатель за пользователямихранится в нем.

когдаmsgПри смене еще какие-то грубые операции в процессе, это не важно, а в итоге одна будет выполненаrunметод, вызовите функцию обратного вызова, поместитеnewValueа такжеoldValueПройти:

// dep  
notify() {
  this.subs.forEach(watcher => watcher.update())
}
// watch
update(){
  if (this.lazy) {
    // 计算属性 需要更新
    this.dirty = true;
  } else if (this.sync) {
    this.run();
  } else {
    // 异步更新 多次修改同一个值 只更新一次
    queueWatcher(this);
  }
  // queueWatcher(this);
  // this.get()
}
run(){
  let oldValue = this.value;
  // 再执行一次就拿到了现在的值,会去重哈,watcher不会重复添加
  let newValue = this.get();
  this.value = newValue;
  if(this.user && oldValue != newValue) { 
    // 是用户watcher, 就调用callback 也就是 handler
    this.callback(newValue, oldValue)
  }
}

computed

computed: {
  c_msg() {
    return this.msg + 'computed'
  }
  // or
  c_msg: {
    get() {
      return this.msg + 'computed'
    },
    set() {}
  }
},

computedКаковы особенности:

  1. будет выполняться при вызове
  2. с кешем
  3. Пересчитывать при изменении зависимостей

Выполняется при вызове, как я узнаю, что он звонит? эй эй,Object.definePropertyРазве это не то, что он делает, это не совпадение.

Когда зависимые данные изменятся, они будут пересчитаны, поэтому вам нужно собрать зависимости. Еще та логика, позвониthis.msg -> get -> dep.depend().

function initComputed(vm) {
  let computed = vm.$options.computed;
  const watchers = vm._computedWatchers = {};
  for(let key in computed) {
    const userDef = computed[key];
    // 获取get方法
    const getter = typeof userDef === 'function' ? userDef : userDef.get;
    // 创建计算属性watcher lazy就是第一次不调用
    watchers[key] = new Watcher(vm, userDef, () => {}, { lazy: true });
    defineComputed(vm, key, userDef)
  }
}
const sharedPropertyDefinition = {
  enumerable: true,
  configurable: true,
  get: () => {},
  set: () => {}
}
function defineComputed(target, key, userDef) {
  if (typeof userDef === 'function') {
      sharedPropertyDefinition.get = createComputedGetter(key)
  } else {
      sharedPropertyDefinition.get = createComputedGetter(userDef.get);
      sharedPropertyDefinition.set = userDef.set;
  }
  // 使用defineProperty定义 这样才能做到使用才计算
  Object.defineProperty(target, key, sharedPropertyDefinition)
}

Следующий абзац является наиболее важным, просто взгляните на вышеизложенное, и вышеприведенное должно поставитьgetспособ узнать, использоватьObject.definePropertyСвяжите это.

class Watcher{
  constructor(vm,exprOrFn,callback,options,isRenderWatcher){
  	... 
    this.dirty = this.lazy;
    // lazy 第一次不执行
    this.value = this.lazy ? undefined : this.get();
    ...
  }
  
  update(){
    if (this.lazy) {
      // 计算属性 需要更新
      this.dirty = true;
    } else if (this.sync) {
      this.run();
    } else {
      queueWatcher(this); // 这就是个陪衬 现在不管它
    }
  }
  evaluate() {
    this.value = this.get();
    this.dirty = false;
  }
}

Кэш здесь, выполнитьgetМетод получит возвращаемое значениеthis.valueэто кэшированное значение, вНаблюдатель за пользователями, этоoldValue, Когда я написал это, мое восхищение Богом Ю Да усилилось. 🐂🍺плюс!

function createComputedGetter(key) {
  return function computedGetter() {
    // this 指向vue 实例
    const watcher = this._computedWatchers[key];
    if (watcher) {
      if (watcher.dirty) { // 如果dirty为true
        watcher.evaluate();// 计算出新值,并将dirty 更新为false
      }
      // 如果依赖的值不发生变化,则返回上次计算的结果
      return watcher.value
    }
  }
}

watcherизupdateКогда это было вызвано? То есть вызов обновления данныхdep.notify(),dirtyдолжен статьtrue, но вычисляемое свойство по-прежнему не может быть вычислено немедленно, его все равно нужно вычислить при вызове, поэтому вupdateтолько что изменилdirtyположение дел! Затем он будет пересчитан при следующем вызове.

Полагаться на изменение данных -> после обновления уведомления -> сбросить флаг грязных данных -> значение обновления (пересчитать) при чтении страницы (выполнение наблюдателя рендеринга).

class Dep {
  constructor() {
    this.id = id ++;
    this.subs = [];
  }
  addSub(watcher) {
    this.subs.push(watcher);
  }
  depend() {
    Dep.target.addDep(this);
  }
  notify() {
    this.subs.forEach(watcher => watcher.update())
  }
}

Суммировать

  1. watchа такжеcomputedПо сутиWatcher, хранятся вDep, при изменении данных выполнитьdep.notifyпоставить текущий соответствующийDepхранится в экземпляреWatcherВсеrunТеперь это сделанооказыватьWatcherСтраница обновляется;
  2. Каждые данные имеют своиDep, если он вызывается в шаблоне, он должен иметь渲染Watcher;
  3. initDataкогда, не так лиWatcherМожно собрать;
  4. не нашел,оказыватьWatcherа такжеComputedсередина,exprOrFnфункции,ПользовательWatcherвсе строки.

Код в статье является сокращенной версией, и есть много деталей, которые не упомянуты. Это не важно и не важно для данной статьи. Для более глубокого понимания вы можете прочитать исходный код.