Vue уже составляет треть текущего отечественного веб-терминала, а также является одним из моих основных технологических стеков. Я знаю его в повседневном использовании, и мне это любопытно. Кроме того, большое количество исходного кода Vue Недавно в сообществе появились классы чтения. В этой статье я воспользовался этой возможностью, чтобы почерпнуть пищу из всех статей и обсуждений. В то же время я обобщил некоторые идеи при чтении исходного кода и подготовил несколько статей в качестве результатов своей работы. собственное мышление.мой уровень ограничен.добро пожаловать, чтобы оставить сообщение для обсуждения~
Целевая версия Vue:2.5.17-beta.0
Комментарии к исходному коду Vue:GitHub.com/Шерлок Эд9…
Отказ от ответственности: Синтаксис исходного кода в статье использует Flow, и исходный код сокращен по мере необходимости (чтобы не путать @_@), если вы хотите увидеть полную версию, пожалуйста, введите вышегитхаб-адрес, эта статья представляет собой серию статей, адрес статьи внизу ~
Заинтересованные студенты могут добавить группу WeChat в конце статьи для совместного обсуждения~
0. Предварительные знания
- Flow
- Синтаксис ES6
- Общие шаблоны проектирования
- Идеи функционального программирования, такие как каррирование
Вот несколько предварительных статей:Инструмент статической проверки типов JS Flow,Начало работы с ECMAScript 6 - Руан Ифэн,Каррирование в JS,Шаблон JS-наблюдателя,JS использует функции высшего порядка для реализации кэширования функций (режим мемо)
1. Структура файла
файловая структура в vueCONTRIBUTING.mdЕсть введение, которое напрямую переведено здесь:
├── scripts ------------------------------- 包含与构建相关的脚本和配置文件
│ ├── alias.js -------------------------- 源码中使用到的模块导入别名
│ ├── config.js ------------------------- 项目的构建配置
├── build --------------------------------- 构建相关的文件,一般情况下我们不需要动
├── dist ---------------------------------- 构建后文件的输出目录
├── examples ------------------------------ 存放一些使用Vue开发的应用案例
├── flow ---------------------------------- JS静态类型检查工具 [Flow](https://flowtype.org/) 的类型声明
├── package.json
├── test ---------------------------------- 测试文件
├── src ----------------------------------- 源码目录
│ ├── compiler -------------------------- 编译器代码,用来将 template 编译为 render 函数
│ │ ├── parser ------------------------ 存放将模板字符串转换成元素抽象语法树的代码
│ │ ├── codegen ----------------------- 存放从抽象语法树(AST)生成render函数的代码
│ │ ├── optimizer.js ------------------ 分析静态树,优化vdom渲染
│ ├── core ------------------------------ 存放通用的,平台无关的运行时代码
│ │ ├── observer ---------------------- 响应式实现,包含数据观测的核心代码
│ │ ├── vdom -------------------------- 虚拟DOM的 creation 和 patching 的代码
│ │ ├── instance ---------------------- Vue构造函数与原型相关代码
│ │ ├── global-api -------------------- 给Vue构造函数挂载全局方法(静态方法)或属性的代码
│ │ ├── components -------------------- 包含抽象出来的通用组件,目前只有keep-alive
│ ├── server ---------------------------- 服务端渲染(server-side rendering)的相关代码
│ ├── platforms ------------------------- 不同平台特有的相关代码
│ │ ├── weex -------------------------- weex平台支持
│ │ ├── web --------------------------- web平台支持
│ │ │ ├── entry-runtime.js ---------------- 运行时构建的入口
│ │ │ ├── entry-runtime-with-compiler.js -- 独立构建版本的入口
│ │ │ ├── entry-compiler.js --------------- vue-template-compiler 包的入口文件
│ │ │ ├── entry-server-renderer.js -------- vue-server-renderer 包的入口文件
│ ├── sfc ------------------------------- 包含单文件组件(.vue文件)的解析逻辑,用于vue-template-compiler包
│ ├── shared ---------------------------- 整个代码库通用的代码
Несколько важных каталогов:
- компилятор:Компиляция, используемая для преобразования шаблона в функцию рендеринга
- основной:Основной код Vue, включая адаптивную реализацию, виртуальную DOM, монтирование методов экземпляра Vue, глобальные методы, абстрактные общие компоненты и т. д.
- Платформа:Входные файлы разных платформ в основном предназначены для веб-платформы и платформы weex.Разные платформы имеют свой собственный специальный процесс построения.Конечно, наше внимание сосредоточено на веб-платформе.
- сервер:Код, связанный с рендерингом на стороне сервера (SSR), SSR в основном отображает компоненты непосредственно в HTML и предоставляет их клиенту напрямую с сервера.
- ПФС:В основном логика разбора файла .vue
- общий:Некоторые общие служебные методы, некоторые настроены для повышения читабельности кода.
Среди них под платформойsrc/platforms/web/entry-runtime.js
Файл используется в качестве точки входа для построения среды выполнения.Метод ESM выводит dist/vue.runtime.esm.js, метод CJS выводит dist/vue.runtime.common.js, а метод UMD выводит dist/vue. runtime.js, за исключением шаблона шаблона для компилятора для функции рендерингаsrc/platforms/web/entry-runtime-with-compiler.js
Файл используется в качестве точки входа для построения среды выполнения: метод ESM выводит dist/vue.esm.js, метод CJS выводит dist/vue.common.js, а метод UMD выводит dist/vue.js, включая компилятор.
2. Входной файл
Любой интерфейсный проект можно скачать сpackage.json
файл, давайте посмотрим на егоscript.dev
что мы бежимnpm run dev
Когда его командная строка:
"scripts": {
"dev": "rollup -w -c scripts/config.js --environment TARGET:web-full-dev"
}
здесьrollupэто сборщик модулей JS, похожий на webpack, на самом деле
Vue - v1.0.10
Предыдущая версия использовала webpack , а затем изменила его на rollup , если вы хотите знать, почему он был изменен на rollup , вы можете проверить это.Вы собственный ответ Юсики, в общем, чтобы уменьшить размер пакета и увеличить скорость инициализации.
Вы можете увидеть здесь накопительный пакет для запускаscripts/config.js
файл и заданный параметрTARGET:web-full-dev
, Давайте посмотримscripts/config.js
что там
// scripts/config.js
const builds = {
'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 // 每个包前面的注释-版本/作者/日期.etc
},
}
Описание метода компиляции формата:
- Эс:Модули ES, вывод с использованием синтаксиса шаблона ES6
- cjs:Модуль CommonJs, вывод файла в соответствии со спецификацией модуля CommonJs.
- и:Модуль AMD, вывод файла в соответствии со спецификацией модуля AMD
- умд:Поддержка вывода файла спецификации внешней ссылки, этот файл может напрямую использовать тег скрипта
здесьweb-full-dev
Это соответствует команде, которую мы только что передали в командной строке, затем накопительный пакет начнет упаковываться в соответствии с файлом записи ниже, и есть много других команд и других методов и форматов вывода.Вы можете проверить исходный код самостоятельно.
Поэтому основное внимание в этой статье уделяется включению компилятора компилятора.src/platforms/web/entry-runtime-with-compiler.js
файл, в среде производства и разработки используем vue-loader для компиляции шаблона, так что пакет с компилятором не нужен, но для лучшего понимания принципа и процесса рекомендуется посмотреть файл входа с компилятор.
Давайте сначала посмотрим на этот файл, где импортируется Vue, чтобы увидеть, откуда он берется.
// src/platforms/web/entry-runtime-with-compiler.js
import Vue from './runtime/index'
Продолжай читать
// src/platforms/web/runtime/index.js
import Vue from 'core/index'
keep moving
// src/core/index.js
import Vue from './instance/index'
keep moving*2
// src/core/instance/index.js
/* 这里就是vue的构造函数了,不用ES6的Class语法是因为mixin模块划分的方便 */
function Vue(options) {
this._init(options) // 初始化方法,位于 initMixin 中
}
// 下面的mixin往Vue.prototype上各种挂载
initMixin(Vue)
stateMixin(Vue)
eventsMixin(Vue)
lifecycleMixin(Vue)
renderMixin(Vue)
export default Vue
когда мыnew Vue( )
Когда , этот конструктор фактически вызывается, и вы можете начать отсюда.
3. Рабочий механизм
Здесь я использую xmind, чтобы нарисовать грубую схему механизма работы, в основном, последующий анализ находится в некоторых частях этой картины.
Все примеры Vue в этой статье используютсяvm
Представлять
Приведенную выше картинку можно разделить на несколько частей для более подробного чтения.Конкретная реализация будет подробно рассмотрена в следующих статьях.
3.1 Initialization_init()
Когда мы в main.jsnew Vue( )
, Vue вызовет конструктор_init( )
метод, этот метод находится в core/instance/index.jsinitMixin( )
определяется в методе
// src/core/instance/index.js
/* 这里就是Vue的构造函数 */
function Vue(options) {
this._init(options) // 初始化方法,位于 initMixin 中
}
// 下面的mixin往Vue.prototype上各种挂载,这是在加载的时候已经挂载好的
initMixin(Vue) // 给Vue.prototype添加:_init函数,...
stateMixin(Vue) // 给Vue.prototype添加:$data属性, $props属性, $set函数, $delete函数, $watch函数,...
eventsMixin(Vue) // 给Vue.prototype添加:$on函数, $once函数, $off函数, $emit函数, $watch方法,...
lifecycleMixin(Vue) // 给Vue.prototype添加: _update方法, $forceUpdate函数, $destroy函数,...
renderMixin(Vue) // 给Vue.prototype添加: $nextTick函数, _render函数,...
export default Vue
мы можем видетьinit( )
Какую инициализацию выполняет этот метод:
// src/core/instance/index.js
Vue.prototype._init = function(options?: Object) {
const vm: Component = this
initLifecycle(vm) // 初始化生命周期 src/core/instance/lifecycle.js
initEvents(vm) // 初始化事件 src/core/instance/events.js
initRender(vm) // 初始化render src/core/instance/render.js
callHook(vm, 'beforeCreate') // 调用beforeCreate钩子
initInjections(vm) // 初始化注入值 before data/props src/core/instance/inject.js
initState(vm) // 挂载 data/props/methods/watcher/computed
initProvide(vm) // 初始化Provide after data/props
callHook(vm, 'created') // 调用created钩子
if (vm.$options.el) { // $options可以认为是我们传给 `new Vue(options)` 的options
vm.$mount(vm.$options.el) // $mount方法
}
}
здесь_init()
метод к текущемуvm
Экземпляр выполняет ряд настроек инициализации, более важным является метод инициализации StateinitState(vm)
когдаdata/props
отзывчивость, это легендарный пасObject.defineProperty()
Метод устанавливает объект, который должен быть отзывчивымgetter/setter
, на этой основе выполняется сбор зависимостей для достижения целей изменения данных, приводящих к изменениям представления.
Окончательная проверкаvm.$options
Есть ли на немel
атрибут, если есть, используйтеvm.$mount
метод монтированияvm
, чтобы сформировать связь между уровнем данных и уровнем представления. Это также, если не предусмотреноel
Опция должна быть сделана вручнуюvm.$mount('#app')
причина.
Мы виделиcreated
Крючок монтируется$mount
звонил раньше, так что мы вcreated
DOM нельзя манипулировать, пока не сработает хук, потому что он еще не отрендерен в DOM.
3.2 Крепление $mount()
Метод крепленияvm.$mount( )
Он определен в нескольких местах и связан с различными методами упаковки и платформами.src/platform/web/entry-runtime-with-compiler.js
,src/platform/web/runtime/index.js
,src/platform/weex/runtime/index.js
, наше внимание сосредоточено на первом файле, но вentry-runtime-with-compiler.js
Сначала файл будет помещенruntime/index.js
середина$mount
Метод сохраняется и запускается с вызовом в конце:
// src/platform/web/entry-runtime-with-compiler.js
const mount = Vue.prototype.$mount // 把原来的$mount保存下来,位于 src/platform/web/runtime/index.js
Vue.prototype.$mount = function(
el?: string | Element, // 挂载的元素
hydrating?: boolean // 服务端渲染相关参数
): Component {
el = el && query(el)
const options = this.$options
if (!options.render) { // 如果没有定义render方法
let template = options.template
// 把获取到的template通过编译的手段转化为render函数
if (template) {
const { render, staticRenderFns } = compileToFunctions(template, {...}, this)
options.render = render
}
}
return mount.call(this, el, hydrating) // 执行原来的$mount
}
В версии Vue 2.0 для рендеринга всех компонентов Vue в конечном итоге требуется метод рендеринга.Независимо от того, разрабатываем ли мы компонент в виде одного файла .vue или пишем атрибут el или template, он в конечном итоге будет преобразован в метод рендеринга. здесьcompileToFunctions
Это метод компиляции шаблона в рендер, который будет представлен позже.
// src/platform/weex/runtime/index.js
Vue.prototype.$mount = function (
el?: string | Element, // 挂载的元素
hydrating?: boolean // 服务端渲染相关参数
): Component {
el = el && inBrowser ? query(el) : undefined // query就是document.querySelector方法
return mountComponent(this, el, hydrating) // 位于core/instance/lifecycle.js
}
здесьel
Если это не элемент DOM в начале, он будет заменен методом запроса с элементом DOM, а затем передаваться вmountComponent
метод, мы продолжаем видетьmountComponent
Определение:
// src/core/instance/lifecycle.js
export function mountComponent (
vm: Component,
el: ?Element,
hydrating?: boolean
): Component {
vm.$el = el
if (!vm.$options.render) {
vm.$options.render = createEmptyVNode
}
callHook(vm, 'beforeMount') // 调用beforeMount钩子
// 渲染watcher,当数据更改,updateComponent作为Watcher对象的getter函数,用来依赖收集,并渲染视图
let updateComponent
updateComponent = () => {
vm._update(vm._render(), hydrating)
}
// 渲染watcher, Watcher 在这里起到两个作用,一个是初始化的时候会执行回调函数
// ,另一个是当 vm 实例中的监测的数据发生变化的时候执行回调函数
new Watcher(vm, updateComponent, noop, {
before () {
if (vm._isMounted) {
callHook(vm, 'beforeUpdate') // 调用beforeUpdate钩子
}
}
}, true /* isRenderWatcher */)
// 这里注意 vm.$vnode 表示 Vue 实例的父虚拟 Node,所以它为 Null 则表示当前是根 Vue 的实例
if (vm.$vnode == null) {
vm._isMounted = true // 表示这个实例已经挂载
callHook(vm, 'mounted') // 调用mounted钩子
}
return vm
}
существуетmountComponent
метод создает экземпляр рендераWatcher
, и прошел вupdateComponent
,Сюда:() => { vm._update(vm._render(), hydrating) }
первое использование_render
генерация методаVNode
, затем позвоните_update
способ обновления DOM. Вы можете ознакомиться с введением раздела обновления просмотра
Здесь вызывается несколько хуков, и можно наблюдать за их синхронизацией.
3.3 Компиляция compile()
Если нам нужно преобразовать сцену рендеринга, такую как написанный нами шаблон, она будет преобразована компилятором в функцию рендеринга, которая состоит из нескольких шагов:
Запись находится только в src/platform/web/entry-runtime-with-compiler.js.compileToFunctions
метод:
// src/platforms/web/compiler/index.js
const { compile, compileToFunctions } = createCompiler(baseOptions)
export { compile, compileToFunctions }
продолжайте видеть здесьcreateCompiler
метод:
// src/compiler/index.js
export const createCompiler = createCompilerCreator(function baseCompile (
template: string,
options: CompilerOptions
): CompiledResult {
const ast = parse(template.trim(), options)
if (options.optimize !== false) {
optimize(ast, options)
}
const code = generate(ast, options)
return {
ast,
render: code.render,
staticRenderFns: code.staticRenderFns
}
})
Здесь можно увидеть три важных процесса.parse
,optimize
,generate
, а затем сгенерируйте код метода рендеринга.
-
parse
: он будет использовать обычные методы для анализа инструкций, классов, стилей и других данных в шаблоне шаблона для формирования абстрактного синтаксического дерева AST. -
optimize
: оптимизация AST, создание шаблона дерева AST, обнаружение статических поддеревьев, не требующих изменений DOM, и снижение нагрузки на исправление. -
generate
: генерировать код метода рендеринга из AST.
3.4 Реактивное наблюдение()
Vue, как инфраструктура MVVM, мы знаем, что мост между его уровнем модели и уровнем представления ViewModel является ключом к управлению данными, отзывчивость Vue достигается за счетObject.definePropertyДля этого установите отзывчивый объектgetter/setter
Когда визуализируется функция рендеринга, запускается объект ответа на чтение.getter
провестиКоллекция зависимостей, и эта настройка срабатывает при изменении адаптивного объекта.setter
,setter
метод будетnotify
все, что он собрал доwatcher
чтобы сообщить им, что их значение было обновлено, что вызываетwatcher
изupdate
идтиpatch
Обновите вид.
Отвечающая запись находится в src/core/instance/init.js.initState
середина:
// src/core/instance/state.js
export function initState(vm: Component) {
vm._watchers = []
const opts = vm.$options
if (opts.props) initProps(vm, opts.props)
if (opts.methods) initMethods(vm, opts.methods)
if (opts.data) {
initData(vm)
} else {
observe(vm._data = {}, true /* asRootData */)
}
if (opts.computed) initComputed(vm, opts.computed)
if (opts.watch && opts.watch !== nativeWatch) {
initWatch(vm, opts.watch)
}
}
Он очень регулярно определяет несколько методов для инициализацииprops
,methods
,data
,computed
,wathcer
, вот только посмотриinitData
метод, взгляните на леопарда
// src/core/instance/state.js
function initData(vm: Component) {
let data = vm.$options.data
data = vm._data = typeof data === 'function'
? getData(data, vm)
: data || {}
observe(data, true /* asRootData */) // 给data做响应式处理
}
Сначала оцените, являются ли данные функцией, если да, возьмите возвращаемое значение, если нет, затем возьмите сами себя, а затемobserve
пара методовdata
Для обработки посмотрите этот метод
// src/core/observer/index.js
export function observe (value: any, asRootData: ?boolean): Observer | void {
let ob: Observer | void
ob = new Observer(value)
return ob
}
Этот метод в основном используетdata
Чтобы создать экземпляр объекта Observer, Observer является классом, конструктор Observer используетdefineReactive
метод для реактивного стиля ключей объекта, он рекурсивно добавляет к свойствам объектаgetter/setter
, для сбора зависимостей и уведомления об обновлении этот метод, вероятно, такой
// src/core/observer/index.js
function defineReactive (obj, key, val) {
Object.defineProperty(obj, key, {
enumerable: true,
configurable: true,
get: function reactiveGetter () {
/* 进行依赖收集 */
return val;
},
set: function reactiveSetter (newVal) {
if (newVal === val) return;
notify(); // 触发更新
}
});
}
3.5 Просмотр патча обновления( )
когда используешьdefineReactive
После того, как метод сделает объект отзывчивым, при рендеринге функции рендеринга он будет считыватьgetter
тем самым вызываяgetter
провестиwatcher
Коллекция зависимостей, которая запускается при изменении значения отзывчивого объекта.setter
уведомлятьnotify
Зависимости, собранные ранее, уведомляют себя о том, что они были изменены, пожалуйста, повторно визуализируйте представление по мере необходимости. уведомленwatcher
передачаupdate
метод для обновления представления, которое передается вnew Watcher( )
изupdateComponent
В методе этот метод называетсяupdate
путьpatch
Обновите вид.
// src/core/instance/lifecycle.js
let updateComponent
updateComponent = () => {
vm._update(vm._render(), hydrating)
}
// 渲染watcher, Watcher 在这里起到两个作用,一个是初始化的时候会执行回调函数
// ,另一个是当 vm 实例中的监测的数据发生变化的时候执行回调函数
new Watcher(vm, updateComponent, noop, {...}, true /* isRenderWatcher */)
это_render
способ создания виртуального узла,_update
метод будет передаваться в новый VNode вместе со старым VNodepatch
// src/core/instance/lifecycle.js
Vue.prototype._update = function(vnode: VNode, hydrating?: boolean) { // 调用此方法去更新视图
const vm: Component = this
const prevVnode = vm._vnode
vm._vnode = vnode
if (!prevVnode) {
// 初始化
vm.$el = vm.__patch__(vm.$el, vnode, hydrating, false /* removeOnly */)
} else {
//更新
vm.$el = vm.__patch__(prevVnode, vnode)
}
}
_update
передача__patch__
метод, который в основном сравнивает старые и новые виртуальные узлыpatchVnode
, после того, как алгоритм diff напрямую рисует их различия, и, наконец, соответствующий DOM этих различий обновляется.
На этом этапе в основном представлен основной процесс. У нас есть общее понимание того, как работает Vue, начиная с создания экземпляра конструктора. Мы обсудим содержание каждой части позже. обсудить~
Эта статья представляет собой серию статей, и более поздние части будут обновлены позже, чтобы добиться прогресса вместе ~
Большинство сообщений в Интернете имеют разную глубину и даже некоторые несоответствия. Следующие статьи являются кратким изложением процесса обучения. Если вы найдете какие-либо ошибки, пожалуйста, оставьте сообщение, чтобы указать ~
Ссылаться на:
PS: Всех приглашаю обратить внимание на мой публичный аккаунт [Front End Afternoon Tea], давайте работать вместе~
Кроме того, вы можете присоединиться к группе WeChat «Front-end Afternoon Tea Exchange Group», нажмите и удерживайте, чтобы определить QR-код ниже, чтобы добавить меня в друзья, обратите вниманиеДобавить группу, я заберу тебя в группу~