[Большие галантереи] Взявшись за руки, я познакомлю вас с исходным кодом vue.

внешний интерфейс исходный код JavaScript Vue.js

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

1. Как начать первый шаг

  • исходный проектcloneСпустившись вниз, следуйтеCONTRIBUTINGсерединаDevelopment Setupв последовательности, выполняемой один за другим
$ npm install 

# watch and auto re-build dist/vue.js
$ npm run dev
  • Научитесь смотреть на файл package.json так, как если бы вы использовали MVVM для просмотра его рендеринга.

теперь, когда$ npm run devкоманда может быть перекомпилированаvue.jsфайл, то мы начинаем сscriptsсерединаdevНачать просмотр.

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

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

Обратите внимание на два ключевых слова в приведенной выше команде.scripts/config.jsиweb-full-dev, Посмотримscript/config.jsэтот файл.

if (process.env.TARGET) {
  module.exports = genConfig(process.env.TARGET)
} else {
  exports.getBuild = genConfig
  exports.getAllBuilds = () => Object.keys(builds).map(genConfig)
}

Вспоминая приведенную выше команду, мы передалиTARGETдаweb-full-dev, то занесите его в метод, вы в итоге увидите вот такойobject

 '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
 },

Хотя мы не знаем, что он здесь делает, давайте аннотируем его семантикой. Теперь, когда у нас есть входной файл, мы продолжаем открывать файлweb/entry-runtime-with-compiler.js. ОК, после открытия этого файла я наконец вижу одно из наших целевых ключевых слов.

import Vue from './runtime/index'

Jianghu правил, продолжайте прыгать в этот файл, и тогда вы увидите:

import Vue from 'core/index'

Вы снова увидели знакомые ключевые слова в первой строке кода?Vue

import Vue from './instance/index'

Открытымinstance/indexЗакончив наш первый шаг, мы нашли файл из package.json в фреймворк и нашлиVueместо определения. Давайте еще раз рассмотрим процесс:

2. Научитесь использовать демо

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

  • Структура проекта имеетexamplesКаталог, давайте также создадим в нем нашу собственную демонстрацию, просто скопируйте каталог и назовите его demo, а затем наш код будет протестирован и просмотрен в этом демо.

    Содержимое index.html выглядит следующим образом:

    <!DOCTYPE html>
    <html>
      <head>
        <title>Demo</title>
        <script src="../../dist/vue.js"></script>
      </head>
      <body>
        <div id="demo">
          <template>
            <span>{{text}}</span>
          </template>
        </div>
        <script src="app.js"></script>
      </body>
    </html>
    

    Содержимое файла app.js выглядит следующим образом:

    var demo = new Vue({
      el: '#demo',
      data() {
        return {
          text: 'hello world!'
        }
      }
    })
    
    

Представьте vue.js

В html демо выше мы ввели dist/vue.js, тогда под окном будетVueObject, на данный момент измените код app.js следующим образом:

console.dir(Vue);

Если вы еще не знаете здесьconsole.dir, и знать толькоconsole.log, то попробуйте сами и запомните отличия.

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

3. Откуда он взялся

Помните, когда мы нашли конец в главе 1VueФайл конструктора? Если не помните, вернитесь и посмотрите, в этой главе мы пройдемся по ним в обратном порядке.Vueкрепление атрибута.

instance(src/core/instance/index.js)

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

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

initMixin(Vue)
stateMixin(Vue)
eventsMixin(Vue)
lifecycleMixin(Vue)
renderMixin(Vue)
  1. initMixin(src/core/instance/init.js)

    Vue.prototype._init = function (options?: Object) {}
    

    во входящемVueПрототип объекта смонтирован_initметод.

  2. stateMixin(src/core/instance/state.js)

    // Object.defineProperty(Vue.prototype, '$data', dataDef)
    // 这里$data只提供了get方法,set方法再非生产环境时会给予警告
    Vue.prototype.$data = undefined;
    // Object.defineProperty(Vue.prototype, '$props', propsDef)
    // 这里$props只提供了get方法,set方法再非生产环境时会给予警告
    Vue.prototype.$props = undefined;
    
    Vue.prototype.$set = set
    Vue.prototype.$delete = del
    
    Vue.prototype.$watch = function() {}
    

    Если вы еще не знаете здесьObject.definePropertyЧто он делает? Мое предложение для вас, что вы можете хорошенько взглянуть на прототип объекта. Это значительно повысит эффективность последующего просмотра кода. В противном случае вы только потратите свое время.

  3. eventsMixin(src/core/instance/events.js)

    Vue.prototype.$on = function() {}
    Vue.prototype.$once = function() {}
    Vue.prototype.$off = function() {}
    Vue.prototype.$emit = function() {}
    
  4. lifecycleMixin(src/core/instance/lifecycle.js)

    Vue.prototype._update = function() {}
    Vue.prototype.$forceUpdate = function () {}
    Vue.prototype.$destroy = function () {}
    
  5. renderMixin(src/core/instance/render.js)

    // installRenderHelpers 
    Vue.prototype._o = markOnce
    Vue.prototype._n = toNumber
    Vue.prototype._s = toString
    Vue.prototype._l = renderList
    Vue.prototype._t = renderSlot
    Vue.prototype._q = looseEqual
    Vue.prototype._i = looseIndexOf
    Vue.prototype._m = renderStatic
    Vue.prototype._f = resolveFilter
    Vue.prototype._k = checkKeyCodes
    Vue.prototype._b = bindObjectProps
    Vue.prototype._v = createTextVNode
    Vue.prototype._e = createEmptyVNode
    Vue.prototype._u = resolveScopedSlots
    Vue.prototype._g = bindObjectListeners
    
    // 
    Vue.prototype.$nextTick = function() {}
    Vue.prototype._render = function() {}
    

После выполнения вышеуказанных 5 методов,instanceсредняя параVueПосле волны безумного выхода прототипа,VueПрототип стал:

Если вы думаете, что это конец? Ответ, конечно, нет. Вернемся к core/index.js, следуя схемам, организованным в главе 1.

Core(src/core/index.js)

import Vue from './instance/index'
import { initGlobalAPI } from './global-api/index'
import { isServerRendering } from 'core/util/env'
import { 
	FunctionalRenderContext 
} from 'core/vdom/create-functional-component'

// 初始化全局API
initGlobalAPI(Vue)

Object.defineProperty(Vue.prototype, '$isServer', {
  get: isServerRendering
})

Object.defineProperty(Vue.prototype, '$ssrContext', {
  get () {
    /* istanbul ignore next */
    return this.$vnode && this.$vnode.ssrContext
  }
})

// expose FunctionalRenderContext for ssr runtime helper installation
Object.defineProperty(Vue, 'FunctionalRenderContext', {
  value: FunctionalRenderContext
})

Vue.version = '__VERSION__'

export default Vue

В порядке выполнения кода посмотримinitGlobalAPI(Vue)Содержание метода:

// Object.defineProperty(Vue, 'config', configDef)
Vue.config = { devtools: true, …}
Vue.util = {
	warn,
	extend,
	mergeOptions,
	defineReactive,
}
Vue.set = set
Vue.delete = delete
Vue.nextTick = nextTick
Vue.options = {
	components: {},
	directives: {},
	filters: {},
	_base: Vue,
}
// extend(Vue.options.components, builtInComponents)
Vue.options.components.KeepAlive = { name: 'keep-alive' …}
// initUse
Vue.use = function() {}
// initMixin
Vue.mixin = function() {}
// initExtend
Vue.cid = 0
Vue.extend = function() {}
// initAssetRegisters
Vue.component = function() {}
Vue.directive = function() {}
Vue.filter = function() {}

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

Продолжайте следовать маршруту, разобранному в первой главе, посмотрим, правильно ли работает время выполнения.VueЧто вы наделали.

runtime(src/platforms/web/runtime/index.js)

Здесь не забудьте сначала начать с макроса, не смотрите на детали каждого метода. в состоянии пройтиdebuggerчтобы приостановить выполнение кода, а затем передать консольconsole.dir(Vue)наблюдать в любое времяVueПеремена,

  1. Здесь для веб-платформы в Vue.config добавлена ​​небольшая волна методов.

    Vue.config.mustUseProp = mustUseProp
    Vue.config.isReservedTag = isReservedTag
    Vue.config.isReservedAttr = isReservedAttr
    Vue.config.getTagNamespace = getTagNamespace
    Vue.config.isUnknownElement = isUnknownElement
    
  2. Добавлены директивы к опциямmodelа такжеshowинструкция:

    // extend(Vue.options.directives, platformDirectives)
    Vue.options.directives = {
    	model: { componentUpdated: ƒ …}
    	show: { bind: ƒ, update: ƒ, unbind: ƒ }
    }
    
    
  3. Добавлены компоненты в опцииTransitionа такжеTransitionGroup:

    // extend(Vue.options.components, platformComponents)
    Vue.options.components = {
    	KeepAlive: { name: "keep-alive" …}
    	Transition: {name: "transition", props: {…} …}
    	TransitionGroup: {props: {…}, beforeMount: ƒ, …}
    }
    
  4. Добавить к прототипу__patch__а также$mount:

    // 虚拟dom所用到的方法
    Vue.prototype.__patch__ = patch
    Vue.prototype.$mount = function() {}
    
  5. И поддержка devtools.

entry(src/platforms/web/entry-runtime-with-compiler.js)

  1. В записи перезаписать$mountметод.

  2. монтировать компилировать,compileToFunctionsметод заключается вtemplateкомпилируется вrenderфункция

    Vue.compile = compileToFunctions
    

резюме

До сих пор мы полностью прошли процесс изменения конструктора Vue в Интернете:

  • Монтировать свойства и методы Vue.prototype через экземпляр.
  • Vue монтирует статические свойства и методы через ядро.
  • Добавлена ​​конфигурация, компоненты и инструкции, относящиеся к случаю платформы === 'web' во время выполнения.
  • Добавить компиляцию в метод $mount через записьtemplateСпособность.

4. Что делать

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

Измените app.js в нашей демонстрации на следующий код:

var demo = new Vue({
  el: '#demo',
  data() {
    return {
      text: 'hello world!'
    }
  }
})

Помните конструктор Vue в instance/init? когда код выполняетсяthis._init(options), то начинаем с_initНачните и начните путешествие этой главы.

Vue.prototype._init = function (options?: Object) {
    const vm: Component = this
    // a uid
    vm._uid = uid++

    let startTag, endTag
    /* istanbul ignore if */
    // 浏览器环境&支持window.performance&非生产环境&配置了performance
    if (process.env.NODE_ENV !== 'production' 
    	&& config.performance && mark) {
      startTag = `vue-perf-start:${vm._uid}`
      endTag = `vue-perf-end:${vm._uid}`
      // 相当于 window.performance.mark(startTag)
      mark(startTag)
    }

    // 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 {
      // 将options进行合并
      vm.$options = mergeOptions(
        resolveConstructorOptions(vm.constructor),
        options || {},
        vm
      )
    }
    /* istanbul ignore else */
    if (process.env.NODE_ENV !== 'production') {
      initProxy(vm)
    } else {
      vm._renderProxy = vm
    }
    // 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. В текущем экземпляре добавьте_uid,_isVueАтрибуты.
  2. Когда не работает, отметьте начало инициализации vue с помощью window.performance.
  3. Поскольку в нашей демонстрации _isComponent не обрабатывается вручную, мы войдем в ветку else и объединим Vue.options с входящими опциями.
  4. добавить к текущему экземпляру_renderProxy,_selfАтрибуты.
  5. инициировать жизненный цикл,initLifecycle
  6. событие инициализации,initEvents
  7. Инициализировать рендеринг,initRender
  8. вызов в жизненном циклеbeforeCreate
  9. Инициализировать введенное значениеinitInjections
  10. инициализированное состояниеinitState
  11. Инициализировать ПредоставитьinitProvide
  12. вызов в жизненном циклеcreated
  13. В непроизводственной среде инициализация логотипа завершена, и он добавляется в текущий экземпляр_nameАтрибуты
  14. в соответствии сoptionsвходящийel, который вызывает текущий экземпляр$mount

Хорошо, у нас есть макрос посмотреть на все_initЗатем мы объединяем нашу демонстрацию, чтобы более подробно рассмотреть влияние каждого шага и конкретного метода вызова.

mergeOptions(src/core/util/options.js)

vm.$options = mergeOptions(
	resolveConstructorOptions(vm.constructor),
	options || {},
	vm
)

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
}

Помните, что в главе 3 среда выполнения в пареVueПосле замены что изменилось в опциях? Если забыли, то вот еще раз напомним:

Vue.options = {
	components: {
   		KeepAlive: { name: "keep-alive" …}
    	Transition: {name: "transition", props: {…} …}
    	TransitionGroup: {props: {…}, beforeMount: ƒ, …}
	},
	directives: {
    	model: { componentUpdated: ƒ …}
		show: { bind: ƒ, update: ƒ, unbind: ƒ }
	},
	filters: {},
	_base: ƒ Vue
}

Сначала мы дизассемблируем приведенный выше код.this.constructorвходящийresolveConstructorOptions, потому что в нашей демонстрации нет операции наследования, поэтому вresolveConstructorOptionsВ методе без ввода if возвращается сразу результат, который находится вruntimeобработано вoptionsопции. иoptionsчто мы звонимnew Vue({})когда входящийoptions. В этот момент метод mergeOptions становится:

vm.$options = mergeOptions(
	{
		components: {
	   		KeepAlive: { name: "keep-alive" …}
	    	Transition: {name: "transition", props: {…} …}
	    	TransitionGroup: {props: {…}, beforeMount: ƒ, …}
		},
		directives: {
	    	model: { componentUpdated: ƒ …}
			show: { bind: ƒ, update: ƒ, unbind: ƒ }
		},
		filters: {},
		_base: ƒ Vue
	},
	{
	  el: '#demo',
	  data: ƒ data()
	},
	vm
)

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

// config.optionMergeStrategies = Object.create(null)
const strats = config.optionMergeStrategies

Глядя внимательно на спину, есть также рядstratsОперации над методами и свойствами монтирования и, в конечном счете,stratsстанет:

На самом деле эти операции монтирования разбросаны по коду, я не понимаю, что Вы Да не поместили их в один метод, чтобы единообразно с ними справляться?

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

function mergeOptions (
  parent: Object,
  child: Object,
  vm?: Component
): Object {
  debugger;
  if (process.env.NODE_ENV !== 'production') {
	 // 根据用户传入的options,检查合法性
    checkComponents(child)
  }

  if (typeof child === 'function') {
    child = child.options
  }
  // 标准化传入options中的props
  normalizeProps(child, vm)
  // 标准化注入
  normalizeInject(child, vm)
  // 标准化指令
  normalizeDirectives(child)
  const extendsFrom = child.extends
  if (extendsFrom) {
    parent = mergeOptions(parent, extendsFrom, 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
}

Потому что мы используем самые простыеhello world, так что вmergeOptions, вы можете начать прямо со строки 30, где инициализируются переменныеoptions, 32 строки, 35 строкforПетли были объединены в соответствии со стратегией объединения соответственно. Увидев это, я вдруг понял, чтоstratsЯвляется ли определить некоторые стандартные стратегии слияния, если они не определены в них, использовать стратегию слияния по умолчаниюdefaultStrat.

Здесь есть небольшая деталь, то есть при зацикливании дочерних опций объединяются только те элементы, которых нет в родительских опциях для повышения эффективности слияния.

Давайте продолжим и рассмотрим описанный выше процесс самым простым способом:

// 初始化合并策略
const strats = config.optionMergeStrategies
strats.el = strats.propsData = function (parent, child, vm, key) {}
strats.data = function (parentVal, childVal, vm) {}
constants.LIFECYCLE_HOOKS.forEach(hook => strats[hook] = mergeHook)
constants.ASSET_TYPES.forEach(type => strats[type + 's'] = mergeAssets)
strats.watch = function(parentVal, childVal, vm, key) {}
strats.props = 
strats.methods = 
strats.inject = 
strats.computed = function(parentVal, childVal, vm, key) {}
strats.provide = mergeDataOrFn

// 默认合并策略
const defaultStrat = function (parentVal, childVal) {
  return childVal === undefined
    ? parentVal
    : childVal
}

function mergeOptions (parent, child, vm) {
	// 本次demo没有用到省略前面代码
	...
	
	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
}

Как, понятнее? Эта демонстрация проходитmergeOptionsПосле этого становится так:

Хорошо, потому что мы здесь, чтобы увидеть_init, поэтому, чтобы попасть сюда, вам нужно очиститьVueС помощью стратегии слияния родительский и дочерний элементы могут быть объединены. Далее продолжаем возвращаться к_initправильноoptionsЧто вы делали после обработки слияния?

initProxy(src/core/instance/proxy.js)

После объединения параметров будет принято решение, что если это нерабочая среда, будет введен метод initProxy.

if (process.env.NODE_ENV !== 'production') {
  initProxy(vm)
} else {
  vm._renderProxy = vm
}
vm._self = vm

С помощью тумана я вошел в файл определения метода и увиделProxyЭто ключевое слово, если вам здесь не ясно, вы можете прочитать ES6 г-на Руана, который упоминается выше.

  • Здесь в непроизводственной среде некоторые ключевые слова в config.keyCodes запрещено назначать.
  • вернулсяvm._renderProxy = new Proxy(vm, handlers),здесьhandlers, так как в наших опциях нет рендера, здесь значение hasHandler.

Для чего используется эта часть?Пока знаю,что есть такая штука.Не отказывайтесь от основной линии,и продолжайте возвращаться к основной линии.

initLifecycle(src/core/instance/lifecycle.js)

Инициализируются свойства, связанные с жизненным циклом.

function initLifecycle (vm) {
  const options = vm.$options
  // 省去部分与本次demo无关代码
  ...
  vm.$parent = undefined
  vm.$root = vm

  vm.$children = []
  vm.$refs = {}

  vm._watcher = null
  vm._inactive = null
  vm._directInactive = false
  vm._isMounted = false
  vm._isDestroyed = false
  vm._isBeingDestroyed = false
}

initEvents(src/core/instance/events.js)

function initEvents (vm) {
  vm._events = Object.create(null)
  vm._hasHookEvent = false
  // 省去部分与本次demo无关代码
  ...
}

initRender(src/core/instance/render.js)

function initRender (vm: Component) {
  vm._vnode = null // the root of the child tree
  vm._staticTrees = null // v-once cached trees
  vm.$slots = {}
  vm.$scopedSlots = {}
  vm._c = (a, b, c, d) => createElement(vm, a, b, c, d, false)
  vm.$createElement= (a, b, c, d) => createElement(vm, a, b, c, d, true)
  vm.$attrs = {}
  vm.$listeners = {}
}

callHook(vm, 'beforeCreate)

Вызов функции жизненного цикла перед созданием

initInjections(src/core/instance/inject.js)

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

initState(src/core/instance/state.js)

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

Здесь вы можете сначала заметить несколько ключевых словObserver,Dep,Watcher. каждыйObserverиметь независимыйDep. оWatcher, Пока не пользовался, но поверьте, скоро увидите.

initProvide(src/core/instance/inject.js)

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

callHook(vm, 'created')

Знай, почему здесьcreatedКогда невозможно манипулировать DOM? Потому что здесь нет фактического рендеринга DOM.

vm.$mount(vm.$options.el)

Перед этим стоит суждение «если», поэтому, когда вы не вnew Vueсерединаoptionsнет входящихelЕсли фактический рендеринг не запускается, вам нужно вызвать его вручную.$mount.

здесь$mountКуда это в итоге пойдет? Вспомните, что мы видели в главе 3.compilerзаниматься вещами? просто прикрытьVue.prototype.$mount, далее входим вместе$mountфункцию, чтобы увидеть, что она делает.

// 只保留与本次相关代码,其余看太多会影响视线
const mount = Vue.prototype.$mount
Vue.prototype.$mount = function (
  el?: string | Element,
  hydrating?: boolean
): Component {
  el = el && query(el)

  const options = this.$options
  if (!options.render) {
    let template = getOuterHTML(el)
    if (template) {
      const { render, staticRenderFns } = compileToFunctions(template, {
        shouldDecodeNewlines,
        shouldDecodeNewlinesForHref,
        delimiters: options.delimiters,
        comments: options.comments
      }, this)
      options.render = render
      options.staticRenderFns = staticRenderFns
    }
  }
  return mount.call(this, el, hydrating)
}

Покрытие здесь$mountРаньше, оригинал$mountпридерживаться вариативностиmount, весь переопределенный метод заключается вtemplateПреобразовать вrenderФункция монтируется наvmизoptions, а затем вызвать исходныйmount. так что помниmountОткуда это? давай тогдаruntime/index, метод очень простой, вызывая жизненный циклmountComponent.

// 依然只保留和本demo相关的内容
function mountComponent (
  vm: Component,
  el: ?Element,
  hydrating?: boolean
): Component {
  vm.$el = el
  callHook(vm, 'beforeMount')

  let updateComponent = () => {
  	vm._update(vm._render(), hydrating)
  }

  new Watcher(vm, updateComponent, noop, {
    before () {
      if (vm._isMounted) {
        callHook(vm, 'beforeUpdate')
      }
    }
  }, true /* isRenderWatcher */)
  hydrating = false

  if (vm.$vnode == null) {
    vm._isMounted = true
    callHook(vm, 'mounted')
  }
  return vm
}

Хорошо, а вот и захватывающая часть,Watcher, оживляет весь ряд вещей, которые мы разложили перед собой. Открытымsrc/core/observer/watcher.js,покажи намWatcherфункция конструктора. ясно видетьWatcherпроцесс. По-прежнему сохраняйте только те методы, на которые нам нужно обратить внимание:

  constructor (vm, expOrFn, cb, options, isRenderWatcher) {
  	this.vm = vm
  	vm._watcher = this
    vm._watchers.push(this)
    this.getter = expOrFn
    this.value = this.get()
  }
  
  get () {
    pushTarget(this)
    let value
    const vm = this.vm
    value = this.getter.call(vm, vm)
    popTarget()
    this.cleanupDeps()
    return value
  }
  1. существуетWatcherВ конструкторе передано в это времяupdateComponentв видеWatherизgetter.
  2. существуетgetПри вызове метода передатьpushTargetметод, текущийWatcherназначить вDep.target
  3. перечислитьgetter, что эквивалентно вызовуvm._update, позвони сначалаvm._render, а на этот разvm._render, на данный момент готовrenderвызывается функция.
  4. renderснова используется в функцииthis.text, так что он будет звонить сноваtextизgetметод, который вызываетdep.depend()
  5. dep.depend()перезвонитWatcherизaddDep, тогдаWatcherзаписал текущийdepпример.
  6. Продолжай звонитьdep.addSub(this),depзаписал текущийWatcherэкземпляр, текущийWatcherдепозитdep.subsсередина.
  7. Вот, кстати, в этот разdemoне использовался, то есть когдаthis.textКогда происходит изменение, оно вызываетObserverсерединаsetметод, который вызываетdep.notify()способ выполненияupdateработать.

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

это все,Vueсистема ответа на данные, черезObserver,Watcher,DepИдеально натянуты вместе. Я также надеюсь, что, пройдя этот процесс, вы сможете иметь определенное представление о реальной картине.

Конечно,$mountЕсть еще один шаг, который я недооценил, то есть эта часть, когда шаблон конвертируется в рендер, когда рендер фактически вызывается, он будет проходить_render, $createElement, __patch__, метод, если вам интересно, вы можете просмотреть файлы в каталоге 'src/core/vdom/', чтобы понятьvueДля использования виртуального дома.

Наконец

Если хотите, вы можете продолжить просмотр статьи автора о части преобразования шаблона vue.«Что Vue делает с шаблоном».