Vue Исходный код Анализ (до создания экземпляра) - Принцип реализации данных реагирования

Vue.js

предисловие

В последней статье примерно объяснялись некоторые конфигурации перед созданием экземпляра Vue.Если вы не видели предыдущую статью, канал находится здесь:Анализ исходного кода Vue — перед созданием экземпляра Vue (1)

В конце прошлой статьи я остановлюсь на этом после того, как скажу это.defineReactiveЭтот метод, этот метод, на самом деле является процессом, который вы можете увидеть в некоторых статьях вне процесса реализации принципа адаптивных данных для vue.

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

текст

приходи первымdefineReactiveИсходный код:

//在Object上定义反应属性。
function defineReactive (
  obj,
  key,
  val,
  customSetter,
  shallow
) {
  var dep = new Dep();

  var property = Object.getOwnPropertyDescriptor(obj, key);
  if (property && property.configurable === false) {
    return
  }
  var getter = property && property.get;
  if (!getter && arguments.length === 2) {
    val = obj[key];
  }
  var setter = property && property.set;

  var childOb = !shallow && observe(val);
  Object.defineProperty(obj, key, {
    enumerable: true,
    configurable: true,
    get: function reactiveGetter () {
      var value = getter ? getter.call(obj) : val;
      if (Dep.target) {
        dep.depend();
        if (childOb) {
          childOb.dep.depend();
          if (Array.isArray(value)) {
            dependArray(value);
          }
        }
      }
      return value
    },
    set: function reactiveSetter (newVal) {
      var value = getter ? getter.call(obj) : val;
      /* eslint-disable no-self-compare */
      if (newVal === value || (newVal !== newVal && value !== value)) {
        return
      }
      /* eslint-enable no-self-compare */
      if (process.env.NODE_ENV !== 'production' && customSetter) {
        customSetter();
      }
      if (setter) {
        setter.call(obj, newVal);
      } else {
        val = newVal;
      }
      childOb = !shallow && observe(newVal);
      dep.notify();
    }
  });
}

Прежде чем объяснять этот исходный код, я хочу начать с двух методов Object.Object.defineProperty() а такжеObject.getOwnPropertyDescriptor()

Хотя многие фронтенд-шишки знают его функции, я считаю, что есть некоторые друзья, которые этого не знают.Я надеюсь, что статьи, которые я пишу, не только передают часть духа внутренней реализации Vue, но также помогут некоторым Xiaobai понять какой-то родной апи.


defineProperty

Объяснение на MDN:

Object.defineProperty() 方法会直接在一个对象上定义一个新属性,或者修改一个对象的现有属性, 并返回这个对象。

Здесь это фактически одно из ядер, используемых для реализации отзывчивых данных, главное, чтобы они обновлялись.Object.defineProperty()Принимает до трех параметров:obj , prop , descriptor:

obj:

要在其上定义属性的对象。

prop:

要定义或修改的属性的名称。

descriptor:

将被定义或修改的属性描述符。

возвращаемое значение:

被传递给函数的对象。

Здесь следует отметить одну вещь:В ES6 из-за специфики типа Symbol использование значения типа Symbol в качестве ключа объекта отличается от обычного определения или модификации, а Object.defineProperty — это один из способов определить свойство, ключ которого Символ.

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

И дескрипторы данных, и дескрипторы доступа имеют следующие необязательные ключи:

configurable:

当且仅当该属性的 configurable 为 true 时,该属性描述符才能够被改变,同时该属性也能从对应的对象上被删除。

默认值: false

enumerable:

当且仅当该属性的 enumerable 为 true 时,该属性才能够出现在对象的枚举属性中。

默认为 false。

Дескриптор данных также имеет следующие необязательные ключи:

value:

该属性对应的值。可以是任何有效的 JavaScript 值(数值,对象,函数等)。

默认为 undefined。

writable:

当且仅当该属性的 writable 为 true 时,value 才能被赋值运算符改变。

默认为 false。

Дескриптор доступа также имеет следующие необязательные ключи:

get:

一个给属性提供 getter 的方法,如果没有 getter 则为 undefined。当访问该属性时,该方法会被执行,方法执行时没有参数传入,但是会传入this对象(由于继承关系,这里的this并不一定是定义该属性的对象)。

默认为 undefined。

set:

一个给属性提供 setter 的方法,如果没有 setter 则为 undefined。当属性值修改时,触发执行该方法。该方法将接受唯一参数,即该属性新的参数值。

默认为 undefined。

Object.getOwnPropertyDescriptor()

obj:

需要查找的目标对象

prop:

目标对象内属性名称(String类型)

descriptor:

将被定义或修改的属性描述符。

возвращаемое значение:

返回值其实就是 Object.defineProperty() 中的那六个在 descriptor
对象中可设置的属性,这里就不废话浪费篇幅了,大家看一眼上面就好

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


Dep

var dep = new Dep();

при входеdefineReactiveЭта функция создает экземпляр конструктора Dep и указывает его на переменную с именем dep. Давайте посмотрим, что делает конструктор Dep:

var uid = 0;

var Dep = function Dep () {
  this.id = uid++;
  this.subs = [];
};

Dep.prototype.addSub = function addSub (sub) {
  this.subs.push(sub);
};

Dep.prototype.removeSub = function removeSub (sub) {
  remove(this.subs, sub);
};

Dep.prototype.depend = function depend () {
  if (Dep.target) {
    Dep.target.addDep(this);
  }
};

Dep.prototype.notify = function notify () {
  var subs = this.subs.slice();
  for (var i = 0, l = subs.length; i < l; i++) {
    subs[i].update();
  }
};

Dep.target = null;

Перед созданием экземпляра Dep добавьте в Dep целевой атрибут, значение по умолчанию — null;

Когда Dep создается, он объявляетidАтрибуты каждого экземпляра DEP является уникальным идентификатором;

затем объявляетsubsпустой массив ,subsЧто нужно сделать, так это собрать все зависимости;

addSub:

Буквально вы также можете видеть, что это действие по добавлению зависимостей;

removeSub:

По сути удаляется определенная зависимость, но реализация не пишется в текущем методе, а вызывается метод удаления:

function remove (arr, item) {
  if (arr.length) {
    var index = arr.indexOf(item);
    if (index > -1) {
      return arr.splice(index, 1)
    }
  }
}

Этот метод удаляет элемент из массива;

depend:

Добавьте элемент массива зависимостей;

notify:

Уведомлять каждый элемент массива, обновлять каждый метод;

这里 subs 调用了 slice 方法,官方注释是 “ stabilize the subscriber list first ” 字面意思是 “首先稳定订户列表”,这里我不是很清楚,如果知道的大佬,还请指点一下

Dep.targetПеред созданием экземпляра Vue он всегда равен нулю.Только после создания экземпляра Vue создается экземпляр конструктора Watcher, и он изменится при вызове метода get Watcher.Dep.targetNot null , потому что Watcher включает в себя много контента, поэтому я собираюсь взять одну главу и объяснить ее после создания экземпляра Vue.Теперь мы временно будем рассматривать ее какDep.targetне ноль.

Сейчас,DepКонструктор почти объяснен, продолжим смотреть вниз:

var property = Object.getOwnPropertyDescriptor(obj, key);

Метод возвращает дескриптор свойства, соответствующий собственному свойству указанного объекта, и присваивает его свойству;

if (property && property.configurable === false) {
    return
}

Когда мы хотим внедрить адаптивные данные, нам нужно посмотреть, есть ли у текущего объекта это свойство, которое мы хотим реализовать.

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

var getter = property && property.get;

if (!getter && arguments.length === 2) {
    val = obj[key];
}

Получить метод get текущего свойства.Если такого метода нет и есть только два параметра (obj и ключ), то val получается непосредственно из текущего объекта.

var setter = property && property.set;

Получите метод set текущего свойства.

var childOb = !shallow && observe(val);

Чтобы определить, требуется ли поверхностная копия, если передано false, то необходимо выполнить глубокую копию, а в это время в методObserv необходимо передать текущее значение:

observe

function observe (value, asRootData) {
  if (!isObject(value) || value instanceof VNode) {
    return
  }
  var ob;
  if (hasOwn(value, '__ob__') && value.__ob__ instanceof Observer) {
    ob = value.__ob__;
  } else if (
    shouldObserve &&
    !isServerRendering() &&
    (Array.isArray(value) || isPlainObject(value)) &&
    Object.isExtensible(value) &&
    !value._isVue
  ) {
    ob = new Observer(value);
  }
  if (asRootData && ob) {
    ob.vmCount++;
  }
  return ob
}

существуетdefineReactive, вызовobserveметоду передается только один параметр, поэтому здесь только одно значение, а второе значение на самом деле является логическим значением, которое используется для определения того, являются ли это корневыми данными;

function isObject (obj) {
    return obj !== null && typeof obj === 'object'
}

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

var hasOwnProperty = Object.prototype.hasOwnProperty;
function hasOwn (obj, key) {
  return hasOwnProperty.call(obj, key)
}

Это используется для определения того, имеет ли объект это свойство и указывает ли прототип свойства объекта на Observer;

Если это так, что указывает на то, что значение существовало раньше, то переменная ob равна текущему наблюдаемому экземпляру;

Если нет, сделайте следующее суждение:

var shouldObserve = true;
function toggleObserving (value) {
    shouldObserve = value;
}

shouldObserve используется для определения того, следует ли за ним наблюдать, по умолчанию следует наблюдать;

var _isServer;
var isServerRendering = function () {
  if (_isServer === undefined) {
    /* istanbul ignore if */
    if (!inBrowser && !inWeex && typeof global !== 'undefined') {
      // detect presence of vue-server-renderer and avoid
      // Webpack shimming the process
      _isServer = global['process'] && global['process'].env.VUE_ENV === 'server';
    } else {
      _isServer = false;
    }
  }
  return _isServer
};

Поддерживать ли рендеринг на стороне сервера;

Array.isArray(value)

Является ли текущее значение массивом;

isPlainObject(value)

Используется для определения, является ли он Объектом; конкретный код описан в предыдущей статье, а запись находится здесь:Анализ исходного кода Vue — перед созданием экземпляра Vue (1)

Object.isExtensible(value)

Определить, является ли объект масштабируемым

value._isVue

Чтобы определить, можно ли его наблюдать, инициализация выполняется вinitMixinОн инициализируется в методе, поэтому я пока не буду вдаваться в подробности.

Общий смысл такого количества суждений состоит в том, чтобы судить о том, соблюдается ли текущее значение, если нет, то создать новое и присвоить его переменной ob;

Если asRootData имеет значение true и ob также существует, то добавьте 1 к vmCount;

Наконец возвращается ob.


Затем запустите основную часть кода реактивных данных:

Object.defineProperty(obj, key, {
    enumerable: true,
    configurable: true,
    get: function reactiveGetter () {
    },
    set: function reactiveSetter (newVal) {
    }
});

Во-первых, убедитесь, что отслеживаемое свойство является перечисляемым и модифицируемым;


get

var value = getter ? getter.call(obj) : val;

Предварительно передать метод get текущего свойства в геттерную переменную.Если геттерная переменная существует, указать this текущего геттера на текущий объект и передать его в переменную-значение, если его нет, то получить текущий Метод val передается в переменную value;

if (Dep.target) {
    dep.depend();
    if (childOb) {
      childOb.dep.depend();
      if (Array.isArray(value)) {
        dependArray(value);
      }
    }
}
return value

Каждый раз в get судите, пустой ли Dep.target, если нет, то добавляйте зависимость и вызывайте метод depend экземпляра объекта dep.Здесь в конструкторе Watcher тоже делается какая-то особая обработка.Когда я объясняю Watcher , я возьму его здесь и поговорим об этом вместе.

В любом случае, помните, просто добавьте зависимость, когда получите ее.

Если есть дочерний элемент, и добавьте зависимость к дочернему элементу:

function dependArray (value) {
  for (var e = (void 0), i = 0, l = value.length; i < l; i++) {
    e = value[i];
    e && e.__ob__ && e.__ob__.dep.depend();
    if (Array.isArray(e)) {
      dependArray(e);
    }
  }
}

Если текущее значение является массивом, то нам нужно добавить обработчик этого массива, т.к. сам массив не поддерживает метод defineProperty;

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


set

var value = getter ? getter.call(obj) : val;

Здесь, как и при get, получается текущее значение, а если оно не существует, возвращается значение, полученное функцией;

if (newVal === value || (newVal !== newVal && value !== value)) {
    return
}
if (process.env.NODE_ENV !== 'production' && customSetter) {
    customSetter();
}
if (setter) {
    setter.call(obj, newVal);
} else {
    val = newVal;
}
childOb = !shallow && observe(newVal);
dep.notify();

Если текущее значение совпадает с новым значением, это означает, что изменений нет, поэтому его не нужно менять, просто верните его напрямую;

Если он есть в среде разработки и есть метод customSetter, то вызовите его;

Если текущее свойство имеет метод set, укажите метод set на obj и передайте newVal;

Если он не существует, то напрямую перезаписать значение;

Если это не поверхностная копия, то передайте текущее новое значение методу наблюдения, чтобы проверить, было ли оно замечено, и перезапишите новое значение в childOb;

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


обобщать

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

  • Каждый раз, когда отслеживается свойство, сначала должен быть создан экземпляр очереди Dep, который отвечает за мониторинг зависимостей и уведомление о зависимостях;
  • Подтвердить, существует ли в настоящее время отслеживаемое свойство и может ли оно быть изменено;
  • Если параметр val не получен, а получено только 2 параметра, то сразу установить val в значение текущего свойства, если оно не существует, то оно не определено;
  • Определите, требуется ли текущему отслеживаемому значению глубокая или поверхностная копия.Если это глубокая копия, проверьте, отслеживается ли текущее значение, и если оно не отслеживается, то создайте экземпляр объекта монитора;
  • При вызове метода get получается значение текущего свойства, а если оно не существует, принимается значение, полученное при вызове метода;
  • Проверить текущую очередь, какой объект изменить, и добавить зависимость, если есть цель для проверки;
  • Если есть экземпляр наблюдения, проверьте, является ли текущее значение массивом.Если это массив, то выполните проверку зависимости элемента массива;
  • При обновлении значения обнаруживается, что текущее значение и значение, подлежащее изменению, совпадают, тогда никакая операция не выполняется;
  • Если это в среде разработки, также будет выполнен обратный вызов, который выполняется до изменения значения, но при выполнении условий изменения;
  • Если у текущего свойства есть метод установки, то передайте текущее значение методу установки, и пусть this текущего метода установки указывает на текущий объект.Если он не существует, просто перезапишите старое значение новым значением;
  • Если это глубокая копия, проверьте, соблюдается ли текущее значение, а если не соблюдается, наблюдайте; (Возможно, вы обнаружили, что оно уже наблюдалось один раз, зачем его выполнять? наблюдается во время инициализации. Когда значение изменяется, например, при изменении типа, необходимо повторно наблюдать, чтобы убедиться, что если оно изменено на значение, подобное массиву, также может быть выполнена двусторонняя привязка)
  • Наконец, уведомите все местоположения, которые добавляют зависимости от этого свойства.

заключительные замечания

Ответные данные, соответствующие vue, суммированы здесь.В будущем, когда будет создан экземпляр объекта vue, будет много мест, связанных с ответными данными, поэтому я предлагаю вам взглянуть здесь.

Что касается исходного кода, нам просто нужно понять мышление автора. Мы не должны писать его полностью в соответствии с методом письма автора. Нам нужно изучить его программное мышление, а не его метод письма. На самом деле, я не думаю, что письмо очень хорошо во многих местах. Уместно, но я не совсем понимаю, почему я это делаю. Может быть, мой уровень все еще относительно низок, и я не освещал его. Далее я обобщу эти вопросы и изучите, почему я это делаю.Если это не уместно, я буду при добавлении вопросов в github, ссылка будет выброшена для вашего ознакомления и изучения.

В конце концов, это все еще старая поговорка, мол, подписывайтесь, а если у вас есть вопросы, просто начните распыляться в области комментариев.