Почему свойство данных в Vue (ES6) не может быть объектом?

внешний интерфейс JavaScript Vue.js Babel

Ниже приводится оригинальный текст официального сайта: когда компонент определен, данные должны быть объявлены как функция, которая возвращает исходный объект данных, поскольку компонент может использоваться для создания нескольких экземпляров. Если бы данные по-прежнему были чистым объектом, все экземпляры имели бы общую ссылку на один и тот же объект данных! Предоставляя функцию данных, каждый раз, когда создается новый экземпляр, мы можем вызывать функцию данных, которая возвращает новую копию исходного объекта данных.

В последнее время на собеседование пришло много людей. Я всегда задаю вопрос: «Почему во Vue данные — это метод, возвращающий объект, а не присваивающий его непосредственно объекту?», лишь немногие ответят, что они боятся многократного создания экземпляров и заставляют несколько экземпляров совместно использовать объект данных. . Больше людей отвечают, что не знают, или что официальная документация требует, чтобы это было написано так.

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

Во-первых, в исходном коде Vue есть такая обработка:

  // vue/src/core/instance/state.js
  function initData (vm: Component) {
    var data = vm.$options.data;
    data = vm._data = typeof data === 'function'
      ? getData(data, vm)
      : data || {};
    if (!isPlainObject(data)) {
      data = {};
      process.env.NODE_ENV !== 'production' && warn(
        'data functions should return an object:\n' +
        'https://vuejs.org/v2/guide/components.html#data-Must-Be-a-Function',
        vm
      );
    }
    ...
  }

Очевидно, что vue поддерживает использование объекта в качестве значения атрибута данных в параметре построения vue, и, если данные являются методом, он сначала получит результат объекта, возвращаемый внутренне. И в vuex есть такое использование:

  // vuex/src/store.js
  function resetStoreVM (store, state, hot) {
    ...
    const silent = Vue.config.silent
    Vue.config.silent = true
    store._vm = new Vue({
      data: {
        ?state: state
      },
      computed
    })
    ...
  }

Что тут происходит? Поскольку он поддерживается, он не позволит нам его использовать, а когда мы находимся в vue-файле, прямое присвоение объекта данным вызовет красное предупреждение:

  [Vue warn]: The "data" option should be a function that returns a per-instance value in component definitions.

Это предупреждение исходит из vue/src/core/util/options.js в исходном коде Vue.

  strats.data = function (
    parentVal: any,
    childVal: any,
    vm?: Component
  ): ?Function {
    if (!vm) {
      if (childVal && typeof childVal !== 'function') {
        process.env.NODE_ENV !== 'production' && warn(
          'The "data" option should be a function ' +
          'that returns a per-instance value in component ' +
          'definitions.',
          vm
        )
        return parentVal
      }
      return mergeDataOrFn(parentVal, childVal)
    }
    return mergeDataOrFn(parentVal, childVal, vm)
  }

Прежде всего, нам нужно понять, что процесс создания экземпляра кода из файла vue в компонент vue должен пройти следующие этапы:

  1. Файл vue обрабатывается загрузчиком, шаблон компилируется в функцию рендеринга, а скрипт компилируется в объектную переменную
  2. Передайте скомпилированный объект сценария в рендеринг и вызовите vue.createElement (из vue/src/core/vdom/create-element.js) в функции рендеринга, чтобы построить компонент vue.
  3. В createElement, если это компонент vue, создайте компонент через createComponent (vue/src/core/vdom/create-component.js)
  4. Выньте объектную переменную, скомпилированную сценарием, через $options контекста и используйте Vue.extends (vue/src/core/global-api/extend.js) для создания нового объекта Vue через этот объект.

В 4 из-за использования mergeOptions срабатывала проверка типа данных и отображалось предупреждение в начале.

Так действительно ли присвоение объекту свойств объекта создает общий объект? Давайте посмотрим на код ниже:

  class A {
    constructor(opt) {
      this.opt = opt;
    }

    update() {
      this.opt.data.a++;
    }

    notify() {
      console.log(this.opt);
    }
  }

Мы используем этот класс для виртуализации конструкции Vue. Затем проверьте:

  // test
  let c = new A({ data: { a: 1 }});
  let d = new A({ data: { a: 1 }});

  c.update();
  d.update();
  c.notify(); // Object data: a: 2

Мы передаем свойство объекта для параметра конструктора буквально, но с удивлением обнаруживаем, что нет проблемы с общей ссылкой. Что за чертовщина?

О, нет, когда мы используем vue, мы обычно экспортируем объект в файл vue, а затем этот объект будет скомпилирован и передан в функцию рендеринга после компиляции шаблона во время vue-loader. Итак, давайте проведем эксперимент по-другому:

  // test.js文件,用于虚拟vue文件导出的vue options对象
  export default {
    data: {
      a: 1
    }
  }
  
  // index.js
  let a = new A(test);
  let b = new A(test);

  a.update();
  b.update();
  a.notify(); // Object data: a: 3

Какой? Здесь возникает проблема общих ссылок, упомянутая в документации vue. Почему это?

Причина кроется в процессе компиляции vue и импортированном процессе импорта.При компиляции babel test.js будет преобразован в файл js с синтаксисом es5:

  var Re = {
    data: {
      a: 1
    }
  };
  var Oe = function () {
    function e(t) {
      Object(i["a"])(this, e), this.opt = t
    }
    return Object(o["a"])(e, [{
      key: "update",
      value: function () {
        this.opt.data.a++
      }
    }, {
      key: "notify",
      value: function () {
        console.log(this.opt)
      }
    }]), e
  }(),
  Fe = new Oe(Re),
  Ne = new Oe(Re);
  Fe.update(), Ne.update(), Fe.notify();
  var $e = new Oe({
    data: {
      a: 1
    }
  }),
  Ve = new Oe({
    data: {
      a: 1
    }
  });
  $e.update(), Ve.update(), $e.notify(), 

какие? Получается, что каждый наш vue-файл компилируется babel, напрямую заменяя экспортируемый объект объектной переменной, а затем передавая эту переменную в соответствующий конструктор компонента. Поэтому возникает проблема разделения ссылок (ссылаются на все js-объекты).

Поскольку исходный код vue не был прочитан, сообщите, если есть какие-либо ошибки.