Рукописный исходный код Vue2.0 (10) — Принцип вычисляемых свойств

Vue.js

предисловие

В этой статье в основном написан исходный код Vue2.0 от руки.вычисляемое свойство

В предыдущей статье мы в основном представили VueПринцип свойства прослушиванияЗная, как создаются определяемые пользователем часы В этой статье мы представляем его брата — главная особенность вычисляемого свойства заключается в том, что если значение вычисляемого свойства не изменяется, оно не будет пересчитываться при обновлении страницы. вычисленный результат будет кэширован, и этот API можно использовать для оптимизации производительности.

Для людей:

1. Хотите иметь глубокое понимание исходного кода vue для лучшего ежедневного развития бизнеса

2. Хотите владеть исходным кодом vue framework в резюме (больше не боитесь вопросов интервьюера о серийных убийцах, ха-ха)

3. Учащиеся, у которых нет времени смотреть официальный исходный код или им трудно понять с первого взгляда исходный код


текст

<script>
  // Vue实例化
  let vm = new Vue({
    el: "#app",
    data() {
      return {
        aa: 1,
        bb: 2,
        cc: 3,
      };
    },
    // render(h) {
    //   return h('div',{id:'a'},'hello')
    // },
    template: `<div id="a">hello 这是我自己写的Vue{{computedName}}{{cc}}</div>`,
    computed: {
      computedName() {
        return this.aa + this.bb;
      },
    },
  });
  // 当我们每一次改变数据的时候  渲染watcher都会执行一次 这个是影响性能的
  setTimeout(() => {
    vm.cc = 4;
  }, 2000);
  console.log(vm);
</script>

Приведенный выше пример является основным использованием вычисляемого свойства.Мы изменили cc в шаблоне через две секунды, но aa и bb, от которых зависит вычисляемое свойство, не изменились, поэтому вычисляемое свойство не будет пересчитано, или последний вычисленный результат будет сохранен.

1. Инициализация вычисляемых свойств

// src/state.js

function initComputed(vm) {
  const computed = vm.$options.computed;

  const watchers = (vm._computedWatchers = {}); //用来存放计算watcher

  for (let k in computed) {
    const userDef = computed[k]; //获取用户定义的计算属性
    const getter = typeof userDef === "function" ? userDef : userDef.get; //创建计算属性watcher使用
    // 创建计算watcher  lazy设置为true
    watchers[k] = new Watcher(vm, getter, () => {}, { lazy: true });
    defineComputed(vm, k, userDef);
  }
}

Вычисляемое свойство может быть записано как функция или как объект. Свойство get представляет значение зависимости вычисляемого свойства. Set представляет значение зависимости, которая изменяет вычисляемое свойство. В основном мы заботимся о свойстве get, а затем аналогично Мы помещаем lazy:true в конструктор Watcher, чтобы создать вычисляемое свойство Watcher Итак, что означает defineComputed?

думать? Вычисляемые свойства могут кэшировать результаты вычислений, что делать?

2. Перехват атрибутов вычисляемых свойств

//  src/state.js

// 定义普通对象用来劫持计算属性
const sharedPropertyDefinition = {
  enumerable: true,
  configurable: true,
  get: () => {},
  set: () => {},
};

// 重新定义计算属性  对get和set劫持
function defineComputed(target, key, userDef) {
  if (typeof userDef === "function") {
    // 如果是一个函数  需要手动赋值到get上
    sharedPropertyDefinition.get = createComputedGetter(key);
  } else {
    sharedPropertyDefinition.get = createComputedGetter(key);
    sharedPropertyDefinition.set = userDef.set;
  }
  //   利用Object.defineProperty来对计算属性的get和set进行劫持
  Object.defineProperty(target, key, sharedPropertyDefinition);
}

// 重写计算属性的get方法 来判断是否需要进行重新计算
function createComputedGetter(key) {
  return function () {
    const watcher = this._computedWatchers[key]; //获取对应的计算属性watcher
    if (watcher) {
      if (watcher.dirty) {
        watcher.evaluate(); //计算属性取值的时候 如果是脏的  需要重新求值
      }
      return watcher.value;
    }
  };
}

Метод defineComputed в основном предназначен для переопределения вычисляемого свойства, а самое главное — перехватить метод get, то есть значение зависимости вычисляемого свойства.

Метод createComputedGetter является основой для определения того, зависит ли значение вычисляемого свойства от изменений. Мы добавляем грязный флаг к Watcher, созданному вычисляемым свойством. Если флаг становится истинным, это означает, что для проверки необходимо вызвать watcher.evaluate. перерасчет.

3. Преобразование наблюдателя

// src/observer/watcher.js

// import { pushTarget, popTarget } from "./dep";
// import { queueWatcher } from "./scheduler";
// import {isObject} from '../util/index'
// // 全局变量id  每次new Watcher都会自增
// let id = 0;

export default class Watcher {
  constructor(vm, exprOrFn, cb, options) {
    // this.vm = vm;
    // this.exprOrFn = exprOrFn;
    // this.cb = cb; //回调函数 比如在watcher更新之前可以执行beforeUpdate方法
    // this.options = options; //额外的选项 true代表渲染watcher
    // this.id = id++; // watcher的唯一标识
    // this.deps = []; //存放dep的容器
    // this.depsId = new Set(); //用来去重dep
    // this.user = options.user; //标识用户watcher
    this.lazy = options.lazy; //标识计算属性watcher
    this.dirty = this.lazy; //dirty可变  表示计算watcher是否需要重新计算 默认值是true

    // 如果表达式是一个函数
    // if (typeof exprOrFn === "function") {
    //   this.getter = exprOrFn;
    // } else {
    //   this.getter = function () {
    //     //用户watcher传过来的可能是一个字符串   类似a.a.a.a.b
    //     let path = exprOrFn.split(".");
    //     let obj = vm;
    //     for (let i = 0; i < path.length; i++) {
    //       obj = obj[path[i]]; //vm.a.a.a.a.b
    //     }
    //     return obj;
    //   };
    // }
    // 非计算属性实例化就会默认调用get方法 进行取值  保留结果 计算属性实例化的时候不会去调用get
    this.value = this.lazy ? undefined : this.get();
  }
  get() {
    pushTarget(this); // 在调用方法之前先把当前watcher实例推到全局Dep.target上
    const res = this.getter.call(this.vm); //计算属性在这里执行用户定义的get函数 访问计算属性的依赖项 从而把自身计算Watcher添加到依赖项dep里面收集起来
    popTarget(); // 在调用方法之后把当前watcher实例从全局Dep.target移除
    return res;
  }
  //   把dep放到deps里面 同时保证同一个dep只被保存到watcher一次  同样的  同一个watcher也只会保存在dep一次
  //   addDep(dep) {
  //     let id = dep.id;
  //     if (!this.depsId.has(id)) {
  //       this.depsId.add(id);
  //       this.deps.push(dep);
  //       //   直接调用dep的addSub方法  把自己--watcher实例添加到dep的subs容器里面
  //       dep.addSub(this);
  //     }
  //   }
  //   这里简单的就执行以下get方法  之后涉及到计算属性就不一样了
  update() {
    // 计算属性依赖的值发生变化 只需要把dirty置为true  下次访问到了重新计算
    if (this.lazy) {
      this.dirty = true;
    } else {
      // 每次watcher进行更新的时候  可以让他们先缓存起来  之后再一起调用
      // 异步队列机制
      queueWatcher(this);
    }
  }
  //   计算属性重新进行计算 并且计算完成把dirty置为false
  evaluate() {
    this.value = this.get();
    this.dirty = false;
  }
  depend() {
    // 计算属性的watcher存储了依赖项的dep
    let i = this.deps.length;
    while (i--) {
      this.deps[i].depend(); //调用依赖项的dep去收集渲染watcher
    }
  }
  //   run() {
  //     const newVal = this.get(); //新值
  //     const oldVal = this.value; //老值
  //     this.value = newVal; //跟着之后  老值就成为了现在的值
  //     if (this.user) {
  //       if(newVal!==oldVal||isObject(newVal)){
  //         this.cb.call(this.vm, newVal, oldVal);
  //       }
  //     } else {
  //       // 渲染watcher
  //       this.cb.call(this.vm);
  //     }
  //   }
}

В основном мы смотрим на раскомментированный код, здесь есть четыре основных изменения.

1. При создании экземпляра, если это вычисляемое свойство, оно не будет вызывать метод get для доступа к значению для сбора зависимостей.

2. Метод обновления просто помечает грязь наблюдателя вычислений как истину и будет пересчитываться только при следующем доступе к вычисляемому свойству.

3. Новый метод оценки специально используется для пересчета рассчитанных свойств.

4. Добавьте метод depend, чтобы позволить значению зависимости вычисляемого свойства собирать внешний наблюдатель — этот метод очень важен, мы проанализируем его далее.

4. Коллекция зависимостей внешнего Watcher

// src/state.js

function createComputedGetter(key) {
//   return function () {
//     const watcher = this._computedWatchers[key]; //获取对应的计算属性watcher
//     if (watcher) {
//       if (watcher.dirty) {
//         watcher.evaluate(); //计算属性取值的时候 如果是脏的  需要重新求值
        if (Dep.target) {
    // 如果Dep还存在target 这个时候一般为渲染watcher 计算属性依赖的数据也需要收集
          watcher.depend()
        }
//       }
//       return watcher.value;
//     }
//   };
// }

Это отражает важность метода watcher.depend. Давайте представим, что когда значение нашей зависимости вычисляемого свойства изменяется, наблюдатель загрязняет значение true. В следующий раз, когда мы посещаем вычисляемое свойство, он пересчитывает его, но у нас нет это от начала до конца.Инициировать обновление представления, то есть данные изменились, а представление не было повторно отображено

Почему это?

Потому что в шаблоне есть только вычисляемые свойства, а dep зависимых значений вычисляемых свойств собирает только зависимости самого наблюдателя за вычислениями.Изменения только уведомляют наблюдателя за вычислениями о вызове update и устанавливают для dirty значение true, поэтому мы необходимо найти способ сделать зависимости вычисляемых свойств также. Добавить зависимость наблюдателя рендеринга, чтобы позволить себе измениться, сначала уведомить наблюдателя вычислений для пересчета, а затем уведомить наблюдателя рендеринга для обновления представления

Как это сделать? Давайте посмотрим на код ниже

// src/observer/dep.js

// 默认Dep.target为null
Dep.target = null;
// 栈结构用来存watcher
const targetStack = [];

export function pushTarget(watcher) {
  targetStack.push(watcher);
  Dep.target = watcher; // Dep.target指向当前watcher
}
export function popTarget() {
  targetStack.pop(); // 当前watcher出栈 拿到上一个watcher
  Dep.target = targetStack[targetStack.length - 1];
}

Видимая изначально разработанная наблюдатель контейнеров хранения представляет собой стековую структуру, потому что весь жизненный цикл процесса Vue будет много вычислений наблюдателя наблюдателя, таких как рендеринг и т. д., и каждый слушатель наблюдатель наблюдатель вызывает как до, так и после вызова самого метода попасть в pushTarget popTarget и извлекаются таким образом, когда вычислительное свойство будет немедленно пересчитано в стеке, поэтому внешний слой наблюдателя станет новым Dep.target, давайте использовать метод watcher.depend для вычисления значений свойств, зависящих от внешнего слоя рендеринга. наблюдатель таким образом, когда вычисленное значение изменения, зависящего от атрибута, может быть пересчитано и может обновить представление

Для студентов, которые не знают об обновлениях рендеринга, я предлагаю вам прочитать эту статью.Рукописный исходный код Vue2.0 (четыре) — принцип обновления рендеринга

5. Интеллект-карта вычислительных свойств

Vue2.0源码-计算属性.png

резюме

На данный момент принцип вычисляемых свойств Vue завершен, и между свойствами прослушивания и вычисляемыми свойствами все еще существует большая разница.Вычисляемые свойства обычно используются, когда зависимости должны быть вычислены и могут быть кэшированы. измениться, логика вычисляемых свойств будет выполняться автоматически.Обычно используется в шаблонах для сравнения.Использование многих и прослушивающих свойств заключается в наблюдении за реагирующим значением (вы также можете наблюдать за значением вычисляемого свойства).Как только оно изменится, Вы можете выполнить свой собственный определенный метод. Вы можете посмотреть на карту ума и написать основной код самостоятельно. Ха Ю Не стесняйтесь комментировать или оставлять сообщение, если вы не понимаете или у вас есть споры.

Наконец, если вы найдете эту статью полезной, помнитеКак СанлианБольшое спасибо!

Ссылка на сериал (будет обновлена ​​позже)

Группа передовых рыболовных технологий Brother Shark

Приветствую всех на технических биржахСвязь