Читайте исходный код Vue каждый день (1)

внешний интерфейс Vue.js
Читайте исходный код Vue каждый день (1)

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

Чтение исходного кода должно начинаться с входного файла, и вы хотите найтиVueфайл ввода исходного кода, вы должны сначала начать сpackage.jsonуходить из поля зрения, потому что мы обычно используемnpmКоманда упакована и скомпилирована, поэтому должны быть связанные файлы, на которые можно указать.

"scripts": {
    "dev": "rollup -w -c scripts/config.js --environment TARGET:web-full-dev",
    "dev:cjs": "rollup -w -c scripts/config.js --environment TARGET:web-runtime-cjs-dev",
    "dev:esm": "rollup -w -c scripts/config.js --environment TARGET:web-runtime-esm",
    "dev:test": "karma start test/unit/karma.dev.config.js",
    "dev:ssr": "rollup -w -c scripts/config.js --environment TARGET:web-server-renderer",
    "dev:compiler": "rollup -w -c scripts/config.js --environment TARGET:web-compiler ",
    "dev:weex": "rollup -w -c scripts/config.js --environment TARGET:weex-framework",
    "dev:weex:factory": "rollup -w -c scripts/config.js --environment TARGET:weex-factory",
    "dev:weex:compiler": "rollup -w -c scripts/config.js --environment TARGET:weex-compiler ",
    "build": "node scripts/build.js",
    "build:ssr": "npm run build -- web-runtime-cjs,web-server-renderer",
    "build:weex": "npm run build -- weex",
    "test": "npm run lint && flow check && npm run test:types && npm run test:cover && npm run test:e2e -- --env phantomjs && npm run test:ssr && npm run test:weex",
    "test:unit": "karma start test/unit/karma.unit.config.js",
    "test:cover": "karma start test/unit/karma.cover.config.js",
    "test:e2e": "npm run build -- web-full-prod,web-server-basic-renderer && node test/e2e/runner.js",
    "test:weex": "npm run build:weex && jasmine JASMINE_CONFIG_PATH=test/weex/jasmine.js",
    "test:ssr": "npm run build:ssr && jasmine JASMINE_CONFIG_PATH=test/ssr/jasmine.js",
    "test:sauce": "npm run sauce -- 0 && npm run sauce -- 1 && npm run sauce -- 2",
    "test:types": "tsc -p ./types/test/tsconfig.json",
    "lint": "eslint src scripts test",
    "flow": "flow check",
    "sauce": "karma start test/unit/karma.sauce.config.js",
    "bench:ssr": "npm run build:ssr && node benchmarks/ssr/renderToString.js && node benchmarks/ssr/renderToStream.js",
    "release": "bash scripts/release.sh",
    "release:weex": "bash scripts/release-weex.sh",
    "release:note": "node scripts/gen-release-note.js",
    "commit": "git-cz"
}

Хм, молодец, я был просто в шоке, когда зашел и увидел густую стопку заказов. Однако, чтобы выделить ключевые моменты, взгляните на первую строку с первого взгляда.devКоманда очень правильная, и обнаружено, что она выполняется следующим образом:

rollup -w -c scripts/config.js --environment TARGET:web-full-dev

Через эту строку команды выясняется, что команда найдет этот файл:scripts/config.js, а также естьweb-full-dev, поэтому найдите расположение соответствующего файла и найдите следующий код:

'web-full-dev': {
    entry: resolve('web/entry-runtime-with-compiler.js'),
    dest: resolve('dist/vue.js'),
    format: 'umd',
    env: 'development',
    alias: { he: './entity-decoder' },
    banner
}

ВидетьentryБудет знать, что это запись в расположении файла, описание сделано наполовину, поэтому я следуюresolve('web/entry-runtime-with-compiler.js')Путь, по которому был найден файл и обнаружена проблема:

image.png

Шокировал весь отдел новостей UC, нетwebсодержание!
Сяосяо на некоторое время успокоился и обнаружил, что проблема должна быть вresolveВыше (поскольку узел обычно используется непосредственно таким образом, что вызвало недопонимание). Конечно, нашел здесьresolveявляется пользовательским методом, следующим образом:

const resolve = p => {
  const base = p.split('/')[0]
  if (aliases[base]) {
    return path.resolve(aliases[base], p.slice(base.length + 1))
  } else {
    return path.resolve(__dirname, '../', p)
  }
}

Оказывается, этот путь/Предыдущий является псевдонимом, вalias.jsопределяется следующим образом:

module.exports = {
  vue: resolve('src/platforms/web/entry-runtime-with-compiler'),
  compiler: resolve('src/compiler'),
  core: resolve('src/core'),
  shared: resolve('src/shared'),
  web: resolve('src/platforms/web'),
  weex: resolve('src/platforms/weex'),
  server: resolve('src/server'),
  sfc: resolve('src/sfc')
}

Это довольно ясно, этоentryВ итоге он указывает на:src/platforms/web/entry-runtime-with-compiler.js, так что я не мог дождаться, чтобы открыть этот файл, и обнаружил, что он представилVueдокумент. МОЙ БОГ! Появляется главный герой:

...
import Vue from './runtime/index'
...

следуй, открой сноваruntime/indexОбнаружение файлов было вновь введено:

...
import Vue from 'core/index'
...

оба звонятcoreНа первый взгляд каталог очень сердечный, войдите снова и найдите следующий код:

...
import Vue from './instance/index'
...

Окончательное положение окончательно зафиксированоinstance/indexЭтот файл недоступен, потому что я наконец нашел нашего главного героя:

import { initMixin } from './init'
import { stateMixin } from './state'
import { renderMixin } from './render'
import { eventsMixin } from './events'
import { lifecycleMixin } from './lifecycle'
import { warn } from '../util/index'

function Vue (options) {
  if (process.env.NODE_ENV !== 'production' &&
    !(this instanceof Vue)
  ) {
    warn('Vue is a constructor and should be called with the `new` keyword')
  }
  this._init(options)
}

initMixin(Vue)
stateMixin(Vue)
eventsMixin(Vue)
lifecycleMixin(Vue)
renderMixin(Vue)

export default Vue

Как видно из этого файла, делается две вещи:

  1. определениеVueОбъект: оценивается, генерируется ли он новым и выполняется ли_initСюда;
  2. ПучокVueобъект передается каждомуMinxinметод.

Увидев это, у меня сразу возник вопрос, это_initГде определяется метод и для чего он нужен? Должен~ Если вы хотите узнать ответ, вы должны пойти к тем немногимMixinНайдите в методе. Итак, я начинаю сinitMixinметод, поэтому открытыйinit.jsдокумент:


export function initMixin (Vue: Class<Component>) {
  Vue.prototype._init = function (options?: Object) {
      ...
  }
}

Увидимся, как только вы придетеVueЧан Вэй висит на цепи прототипов_initметод. повернуть ум_initметод, начните изучать:

  1. Сначала найдите определениеvmобъект, указывающий на текущий экземпляр;
const vm: Component = this
  1. Кажется, это рекорд производительности:
// a uid
vm._uid = uid++

let startTag, endTag
/* istanbul ignore if */
if (process.env.NODE_ENV !== 'production' && config.performance && mark) {
  startTag = `vue-perf-start:${vm._uid}`
  endTag = `vue-perf-end:${vm._uid}`
  mark(startTag)
}
  1. Параметры инициализации:
// a flag to avoid this being observed
vm._isVue = true
// merge options
if (options && options._isComponent) {
  // optimize internal component instantiation
  // since dynamic options merging is pretty slow, and none of the
  // internal component options needs special treatment.
  initInternalComponent(vm, options)
} else {
  vm.$options = mergeOptions(
    resolveConstructorOptions(vm.constructor),
    options || {},
    vm
  )
}

первое предложение вышеvm._isVue = trueСудя по комментариям, следует избегать наблюдения (связанного с шаблоном наблюдателя?)
Потом через суд объекты экземпляра обрабатываются по разному.Видя тут я не нашел куда его монтировать._isComponentproperties, так что просто посмотрите прямо ниже.
Нашелvm.$optionsчерезmergeOptionsЭтот метод для объединения параметров, вmergeOptionsесть еще одинresolveConstructorOptionsдля обработки, поэтому я нашел метод и нашел:

export function resolveConstructorOptions (Ctor: Class<Component>) {
  let options = Ctor.options
  if (Ctor.super) {
    const superOptions = resolveConstructorOptions(Ctor.super)
    const cachedSuperOptions = Ctor.superOptions
    if (superOptions !== cachedSuperOptions) {
      // super option changed,
      // need to resolve new options.
      Ctor.superOptions = superOptions
      // check if there are any late-modified/attached options (#4976)
      const modifiedOptions = resolveModifiedOptions(Ctor)
      // update base extend options
      if (modifiedOptions) {
        extend(Ctor.extendOptions, modifiedOptions)
      }
      options = Ctor.options = mergeOptions(superOptions, Ctor.extendOptions)
      if (options.name) {
        options.components[options.name] = Ctor
      }
    }
  }
  return options
}

Открытие заключается в том, чтобы определить, существует лиsuperЭтот атрибут используется для соответствующей операции, по крайней мере, я вижу, что не нашел, куда его примонтировать, поэтому он напрямую возвращает конструктор.options(Где это определено?), отладка обнаружила, что некоторые объекты были смонтированы:

image.png

небо! Откуда это взялось?Можно только сначала сдаться и продолжать смотреть вперед.mergeOptionsЭта функция обнаружила, что ее функция заключается в следующем:

/**
 * Merge two option objects into a new one.
 * Core utility used in both instantiation and inheritance.
 */
export function mergeOptions (
  parent: Object,
  child: Object,
  vm?: Component
): Object {
  if (process.env.NODE_ENV !== 'production') {
    checkComponents(child)
  }

  if (typeof child === 'function') {
    child = child.options
  }

  normalizeProps(child, vm)
  normalizeInject(child, vm)
  normalizeDirectives(child)

  // Apply extends and mixins on the child options,
  // but only if it is a raw options object that isn't
  // the result of another mergeOptions call.
  // Only merged options has the _base property.
  if (!child._base) {
    if (child.extends) {
      parent = mergeOptions(parent, child.extends, vm)
    }
    if (child.mixins) {
      for (let i = 0, l = child.mixins.length; i < l; i++) {
        parent = mergeOptions(parent, child.mixins[i], vm)
      }
    }
  }

  const options = {}
  let key
  for (key in parent) {
    mergeField(key)
  }
  for (key in child) {
    if (!hasOwn(parent, key)) {
      mergeField(key)
    }
  }
  function mergeField (key) {
    const strat = strats[key] || defaultStrat
    options[key] = strat(parent[key], child[key], vm, key)
  }
  return options
}

Выяснено, что это класс инструмента, и аннотация его функции переводится как:

Объединяет два объекта параметров в новый объект.
Основные утилиты для создания экземпляров и наследования.

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

назадinit.jsфайл и нашел этот фрагмент кода:

/* istanbul ignore else */
if (process.env.NODE_ENV !== 'production') {
  initProxy(vm)
} else {
  vm._renderProxy = vm
}

Обнаружил, что в непроизводственной среде я сделалinitProxyОперация, я нажал на метод и увидел, что люди тупые:

config.keyCodes = new Proxy(config.keyCodes, {
  set (target, key, value) {
    if (isBuiltInModifier(key)) {
      warn(`Avoid overwriting built-in modifier in config.keyCodes: .${key}`)
      return false
    } else {
      target[key] = value
      return true
    }
  }
})

как может бытьProxyоперация, это неVue3Используете ли вы его, но, похоже, время отправки истекло раньше...

image.png

Короче, вот вам прокси-операция, вернитесь и посмотрите на другие процессы. существуетinit.jsОстальной код нашелся в файле (Боже, очень раздражает этот способ):

/ expose real self
vm._self = vm
initLifecycle(vm)
initEvents(vm)
initRender(vm)
callHook(vm, 'beforeCreate')
initInjections(vm) // resolve injections before data/props
initState(vm)
initProvide(vm) // resolve provide after data/props
callHook(vm, 'created')

/* istanbul ignore if */
if (process.env.NODE_ENV !== 'production' && config.performance && mark) {
  vm._name = formatComponentName(vm, false)
  mark(endTag)
  measure(`vue ${vm._name} init`, startTag, endTag)
}

if (vm.$options.el) {
  vm.$mount(vm.$options.el)
}

Нашел три вещи сделанные:

  1. Инициализировать жизненный цикл, состояние и т. д. (это должно быть сложно, см. далее);
  2. Тест производительности (используется в сочетании с предыдущим);
  3. Гора согласно суждениюelобъект (это должно быть написаноelнет атрибута$mountПричина, по сути, в$mountсмонтировать).

Я здесь первый, на чтение исходников ушло время, в основном потому, что готовил, и выглядело это трудоемко.
To be continue...