Написанный от руки исходный код Vue2.0 (1) — Принцип отзывчивых данных|Технический обзор

Vue.js
Написанный от руки исходный код Vue2.0 (1) — Принцип отзывчивых данных|Технический обзор

предисловие

Время летит слишком быстро, я полгода не писал статьи, ха-ха, теперь снова ежегодное мероприятиеЗолото три серебро четыреЭто хорошее время, я вернулся, Ли Хансан

Поговорим о сериалепервоначальное намерение-- Компания недавно наняла на руководящие должности фронтенда, так что я познакомился со многими людьми.огорченныйПотому что я обнаружил, что многие студенты, проработавшие более пяти лет, все еще остаются на уровне бизнес-логики.Как правило, у них богатый опыт работы над проектами и они занимались сложным бизнесом, но у них нет хорошего понимания базовой реализации исходного кода. фреймворков или инструментов и деталей js, таких как Vue Миксин (mixin) Все в принципе знают, какую проблему он может решить, но принцип микширования, стратегия микширования, порядок микширования и т. д. На все это можно ответить меньше, чем Решил написать со всеми.Vue2.0Исходный код версии Идеи для изучения отличного исходного кода (версия 3.0 будет добавлена ​​позже)

Для людей:У меня нет времени посмотреть официальный исходный код или посмотреть исходный код, я не хочу видеть одноклассников.

Уведомление: Я вижу, что исходный код всегда поддерживал стратегию 28. Так называемые 20% кода реализуют 80% функций, поэтому в этой серии мы заботимся только о базовой логике и реализации функций.не содержитУпаковка и конструкция Проверка типов Кроссплатформенное уведомление об ошибках Обработка границ Совместимая обработка SSR (рендеринг на стороне сервера) и т. д.

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


текст

Всем известно, что ключевой особенностью Vue является то, чтоуправляемый даннымиЕсли в соответствии с идеей Jquery в прошлом, если наши данные изменяются и хотят синхронизироваться с представлением, мы должны вручную управлять обновлением dom, но Vue помогает нам реализовать функцию автоматического обновления представления, когда данные Внутри Vue должен быть механизм для отслеживания изменения данных и последующего запуска обновления.отзывчивые данныепринцип

1. Инициализация данных

new Vue({
  el: "#app",
  router,
  store,
  render: (h) => h(App),
});

Вы должны быть хорошо знакомы с этим кодом.Это процесс создания экземпляра Vue.Из нового оператора мы можем видеть, что Vue на самом деле является конструктором, ничего особенного, и передаваемый параметр является объектом, который мы называем опциями.

// src/index.js

import { initMixin } from "./init.js";

// Vue就是一个构造函数 通过new关键字进行实例化
function Vue(options) {
  // 这里开始进行Vue初始化工作
  this._init(options);
}
// _init方法是挂载在Vue原型的方法 通过引入文件的方式进行原型挂载需要传入Vue
// 此做法有利于代码分割
initMixin(Vue);
export default Vue;

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

// src/init.js
import { initState } from "./state";
export function initMixin(Vue) {
  Vue.prototype._init = function (options) {
    const vm = this;
    // 这里的this代表调用_init方法的对象(实例对象)
    //  this.$options就是用户new Vue的时候传入的属性
    vm.$options = options;
    // 初始化状态
    initState(vm);
  };
}

initMixin монтирует метод _init в прототипе Vue для вызова экземпляра Vue.

// src/state.js
import { observe } from "./observer/index.js";

// 初始化状态 注意这里的顺序 比如我经常面试会问到 是否能在data里面直接使用prop的值 为什么?
// 这里初始化的顺序依次是 prop>methods>data>computed>watch
export function initState(vm) {
  // 获取传入的数据对象
  const opts = vm.$options;
  if (opts.props) {
    initProps(vm);
  }
  if (opts.methods) {
    initMethod(vm);
  }
  if (opts.data) {
    // 初始化data
    initData(vm);
  }
  if (opts.computed) {
    initComputed(vm);
  }
  if (opts.watch) {
    initWatch(vm);
  }
}

// 初始化data数据
function initData(vm) {
  let data = vm.$options.data;
  //   实例的_data属性就是传入的data
  // vue组件data推荐使用函数 防止数据在组件之间共享
  data = vm._data = typeof data === "function" ? data.call(vm) : data || {};

  // 把data数据代理到vm 也就是Vue实例上面 我们可以使用this.a来访问this._data.a
  for (let key in data) {
    proxy(vm, `_data`, key);
  }
  // 对数据进行观测 --响应式数据核心
  observe(data);
}
// 数据代理
function proxy(object, sourceKey, key) {
  Object.defineProperty(object, key, {
    get() {
      return object[sourceKey][key];
    },
    set(newValue) {
      object[sourceKey][key] = newValue;
    },
  });
}

InitState, мы в основном фокусируемся на наблюдении в initData, который является ядром адаптивных данных, поэтому мы создали еще одну папку наблюдателя, чтобы сосредоточиться на адаптивной логике.

2. Перехват данных объекта

// src/obserber/index.js
class Observer {
  // 观测值
  constructor(value) {
    this.walk(value);
  }
  walk(data) {
    // 对象上的所有属性依次进行观测
    let keys = Object.keys(data);
    for (let i = 0; i < keys.length; i++) {
      let key = keys[i];
      let value = data[key];
      defineReactive(data, key, value);
    }
  }
}
// Object.defineProperty数据劫持核心 兼容性在ie9以及以上
function defineReactive(data, key, value) {
  observe(value); // 递归关键
  // --如果value还是一个对象会继续走一遍odefineReactive 层层遍历一直到value不是对象才停止
  //   思考?如果Vue数据嵌套层级过深 >>性能会受影响
  Object.defineProperty(data, key, {
    get() {
      console.log("获取值");
      return value;
    },
    set(newValue) {
      if (newValue === value) return;
      console.log("设置值");
      value = newValue;
    },
  });
}
export function observe(value) {
  // 如果传过来的是对象或者数组 进行属性劫持
  if (
    Object.prototype.toString.call(value) === "[object Object]" ||
    Array.isArray(value)
  ) {
    return new Observer(value);
  }
}

Ядром перехвата данных является функция defineReactive, которая в основном использует Object.defineProperty для перехвата получения и установки данных. Это решает предыдущую проблему. Почему представление автоматически обновляется при изменении данных? Мы можем уведомлять об обновлении представления в наборе.

Размышление 1. Как этот метод захвата данных влияет на массив?

Таким рекурсивным способом наблюдаются как объекты, так и массивы, но давайте подумаем.Если данные содержат массивы, такие как: [1,2,3,4,5], то мы можем напрямую изменять данные в соответствии с индексом и также триггер set. Но если в массиве тысячи элементов, добавьте методы get и set к индексу каждого элемента, что недоступно для производительности, поэтому этот метод используется только для захвата объектов.

Думая 2. Недостатки Object.defineProperty?

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

3. Наблюдение за массивами

// src/obserber/index.js
import { arrayMethods } from "./array";
class Observer {
  constructor(value) {
    if (Array.isArray(value)) {
      // 这里对数组做了额外判断
      // 通过重写数组原型方法来对数组的七种方法进行拦截
      value.__proto__ = arrayMethods;
      // 如果数组里面还包含数组 需要递归判断
      this.observeArray(value);
    } else {
      this.walk(value);
    }
  }
  observeArray(items) {
    for (let i = 0; i < items.length; i++) {
      observe(items[i]);
    }
  }
}

Поскольку перехват индексов массива слишком расточительно снижает производительность, оценка массива добавляется к параметрам данных, передаваемым конструктором Observer.

// src/obserber/index.js
class Observer {
  // 观测值
  constructor(value) {
    Object.defineProperty(value, "__ob__", {
      //  值指代的就是Observer的实例
      value: this,
      //  不可枚举
      enumerable: false,
      writable: true,
      configurable: true,
    });
  }
}

Прежде чем переписывать прототип массива, мы должны сначала понять этот код.Смысл этого кода заключается в том, чтобы добавить неперечисляемый атрибут __ob__ к каждому отзывчивому данным и указать на экземпляр Observer.Тогда мы можем сначала предотвратить это на основе этого атрибута.Данные то, что наблюдалось в ответ, наблюдается повторно. Во-вторых, в ответных данных можно использовать __ob__ для получения соответствующих методов экземпляра Observer. Это критически важно для массивов.

// src/obserber/array.js
// 先保留数组原型
const arrayProto = Array.prototype;
// 然后将arrayMethods继承自数组原型
// 这里是面向切片编程思想(AOP)--不破坏封装的前提下,动态的扩展功能
export const arrayMethods = Object.create(arrayProto);
let methodsToPatch = [
  "push",
  "pop",
  "shift",
  "unshift",
  "splice",
  "reverse",
  "sort",
];
methodsToPatch.forEach((method) => {
  arrayMethods[method] = function (...args) {
    //   这里保留原型方法的执行结果
    const result = arrayProto[method].apply(this, args);
    // 这句话是关键
    // this代表的就是数据本身 比如数据是{a:[1,2,3]} 那么我们使用a.push(4)  this就是a  ob就是a.__ob__ 这个属性就是上段代码增加的 代表的是该数据已经被响应式观察过了指向Observer实例
    const ob = this.__ob__;

    // 这里的标志就是代表数组有新增操作
    let inserted;
    switch (method) {
      case "push":
      case "unshift":
        inserted = args;
        break;
      case "splice":
        inserted = args.slice(2);
      default:
        break;
    }
    // 如果有新增的元素 inserted是一个数组 调用Observer实例的observeArray对数组每一项进行观测
    if (inserted) ob.observeArray(inserted);
    // 之后咱们还可以在这里检测到数组改变了之后从而触发视图更新的操作--后续源码会揭晓
    return result;
  };
});

4. Интеллект-карта адаптивных данных

图片

резюме

На данный момент принцип отзывчивых данных Vue завершен. Вы можете посмотреть на карту разума и написать основной код самостоятельно. Следует отметить, что на это есть много ссылок в разных средах. Это только отзывчивое наблюдение за data, но как повторно отобразить представление после изменения данных, необходимо объединить с Watcher и dep, чтобы использовать режим наблюдателя для реализации процесса сбора зависимостей и обновления распределения.

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

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

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

Приветствую всех на технических биржахСсылка на сайт