Прежде чем приступить к работе, необходимо иметь прочные базовые навыки чтения исходного кода, а также терпение, чтобы вникнуть в общую ситуацию, не вычитать деталей!
Сначала загляните в каталог
├── build --------------------------------- 构建相关的文件
├── dist ---------------------------------- 构建后文件的输出目录
├── examples ------------------------------ 存放使用Vue开发的的例子
├── flow ---------------------------------- 类型声明,使用开源项目 [Flow](https://flowtype.org/)
├── package.json -------------------------- 项目依赖
├── test ---------------------------------- 包含所有测试文件
├── src ----------------------------------- 这个是我们最应该关注的目录,包含了源码
│ ├──platforms --------------------------- 包含平台相关的代码
│ │ ├──web ----------------------------- 包含了不同构建的包的入口文件
│ │ | ├──entry-runtime.js ---------------- 运行时构建的入口,输出 dist/vue.common.js 文件,不包含模板(template)到render函数的编译器,所以不支持 `template` 选项,我们使用vue默认导出的就是这个运行时的版本。大家使用的时候要注意
│ │ | ├── entry-runtime-with-compiler.js -- 独立构建版本的入口,输出 dist/vue.js,它包含模板(template)到render函数的编译器
│ ├── compiler -------------------------- 编译器代码的存放目录,将 template 编译为 render 函数
│ │ ├── parser ------------------------ 存放将模板字符串转换成元素抽象语法树的代码
│ │ ├── codegen ----------------------- 存放从抽象语法树(AST)生成render函数的代码
│ │ ├── optimizer.js ------------------ 分析静态树,优化vdom渲染
│ ├── core ------------------------------ 存放通用的,平台无关的代码
│ │ ├── observer ---------------------- 反应系统,包含数据观测的核心代码
│ │ ├── vdom -------------------------- 包含虚拟DOM创建(creation)和打补丁(patching)的代码
│ │ ├── instance ---------------------- 包含Vue构造函数设计相关的代码
│ │ ├── global-api -------------------- 包含给Vue构造函数挂载全局方法(静态方法)或属性的代码
│ │ ├── components -------------------- 包含抽象出来的通用组件
│ ├── server ---------------------------- 包含服务端渲染(server-side rendering)的相关代码
│ ├── sfc ------------------------------- 包含单文件组件(.vue文件)的解析逻辑,用于vue-template-compiler包
│ ├── shared ---------------------------- 包含整个代码库通用的代码
2. Что такое конструктор Vue
использоватьnew
оператор для звонкаVue
,Vue
Является конструктором, разберитесь со структурой каталогов, давайте посмотрим на файл входа
Открытымpackage.json
Когда мы запускаем npm run dev, чтобы посмотреть, что мы сделали, rollup также является инструментом упаковки, похожим на webpack, согласно
TARGET=web-full-dev
Откройте найденный файл записиweb/entry-runtime-with-compiler.js
По указанному выше пути поиска мы нашли конструктор Vue
Определите конструктор, введите зависимости, вызовите функцию инициализации и, наконец, экспортируйте Vue.
initMixin(Vue)
stateMixin(Vue)
eventsMixin(Vue)
lifecycleMixin(Vue)
renderMixin(Vue)
export default Vue
Откройте эти пять файлов и найдите соответствующие методы. Вы обнаружите, что функция этих методов состоит в том, чтобы монтировать методы или свойства в прототип прототипа Vue.
1. Сначала войдите в initMixin (Vue) и смонтируйте его на прототип
Vue.prototype._init = function (options) {}
2. Войдите в stateMixin(Vue) и смонтируйте его на прототипе
Vue.prototype.$data
Vue.prototype.$props
Vue.prototype.$set = set
Vue.prototype.$delete = del
Vue.prototype.$watch = function(){}
3. Введите eventsMixin (Vue) и смонтируйте его на прототип
Vue.prototype.$on
Vue.prototype.$once
Vue.prototype.$off
Vue.prototype.$emit
4. Войдите в lifecycleMixin (Vue) и смонтируйте его на прототипе
Vue.prototype._update
Vue.prototype.$forceUpdate
Vue.prototype.$destroy
5. Наконец, введите renderMixin (Vue) и смонтируйте его на прототипе.
Vue.prototype.$nextTick
Vue.prototype._render
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
Следующий шаг к src/core/index.js в соответствии с указанным выше путем поиска
Внедрение зависимостей, монтирование статических методов и свойств в Vue.
-
import { initGlobalAPI } from './global-api/index' import { isServerRendering } from 'core/util/env' initGlobalAPI(Vue) Object.defineProperty(Vue.prototype, '$isServer', { get: isServerRendering }) Object.defineProperty(Vue.prototype, '$ssrContext', { get () { /* istanbul ignore next */ return this.$vnode && this.$vnode.ssrContext } }) Vue.version = '__VERSION__' export default Vue
Войдите в initGlobalAPI (Vue), смонтируйте статические свойства и методы в Vue.
Vue.config Vue.util = util
Vue.set = set
Vue.delete = del
Vue.nextTick = util.nextTick
Vue.options = {
components: { KeepAlive },
directives: {},
filters: {},
_base: Vue
}
Vue.use
Vue.mixin
Vue.cid = 0
Vue.extend
Vue.component = function(){}
Vue.directive = function(){}
Vue.filter = function(){}
затем смонтировать
Vue.prototype.$isServer
Vue.version = '__VERSION__'
В соответствии с указанным выше путем поиска перейдите в runtime/index.js и установите инструменты для конкретной платформы.
Vue.config.mustUseProp = mustUseProp
Vue.config.isReservedTag = isReservedTag
Vue.config.isReservedAttr = isReservedAttr
Vue.config.getTagNamespace = getTagNamespace
Vue.config.isUnknownElement = isUnknownElement
// 安装平台特定的 指令 和 组件
Vue.options = {
components: {
KeepAlive,
Transition,
TransitionGroup
},
directives: {
model,
show
},
filters: {},
_base: Vue
}
Vue.prototype.__patch__
Vue.prototype.$mount
В соответствии с последним шагом указанного выше пути поиска перейдите в web/entry-runtime-with-compiler.js.
- кеш из
web-runtime.js
документ$mount
функция, const mount = Vue.prototype.$mount - Смонтировать на Vue
compile
затем перезапишитеVue.prototype.$mount
- Vue.compile = compileToFunctions — это шаблон
template
Скомпилируйте в функцию рендеринга.
На этом этапе весь конструктор Vue восстанавливается.
3. Объясните весь процесс на примере
index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Vue.js grid component example</title>
</head>
<body>
<div id="app">
<ol>
<li v-for="todo in todos">
{{ todo.text }}
</li>
</ol>
<Child></Child>
</div>
</body>
</html>
grid.js
let Child = {
data: function() {
return {child: '你好哈'}
},
template: '<div>{{child}}</div>'
}
new Vue({
el: '#app',
data: {
todos: [
{text: '学习 JavaScript'},
{text: '学习 Vue'},
{text: '整个牛项目'}]
},
components: {'Child': Child}
})
Чтобы узнать, что делает Vue, сначала посмотрите на функцию-конструктор Vue (параметры) { this._init(параметры) }
- new Vue({//передаем указанный выше контент}) сначала введите Vue.prototype._init, первый метод конструктора для монтирования
Vue.prototype._init = function (options) {
const vm= this
vm._uid = uid++
let startTag, endTag
vm._isVue = true
if (options && options._isComponent) {
initInternalComponent(vm, options)
} else {
vm.$options = mergeOptions(
resolveConstructorOptions(vm.constructor),
options || {},
vm
)
}
vm._renderProxy = vm
vm._self = vm
initLifecycle(vm)
initEvents(vm)
initRender(vm)
callHook(vm, 'beforeCreate')
initInjections(vm)
initState(vm)
initProvide(vm)
callHook(vm, 'created')
if (vm.$options.el) {
vm.$mount(vm.$options.el)
}
}
_init()
В начале метода вthis
В объекте определены два свойства:_uid
и_isVue
, а затем определить, существует ли определениеoptions._isComponent
пойдет сюдаelse
Branch, то есть этот код:
vm.$options = mergeOptions(
resolveConstructorOptions(vm.constructor),
options || {},
vm
)mergeOptions使用策略模式合并传入的options和Vue.options
mergeOptions использует режим стратегии для объединения входящих параметров и объединенной структуры кода Vue.options, Вы можете видеть, что глобальные наследуются путем слияния компонентов стратегии, директив и фильтров. Вот почему глобально зарегистрированный может использоваться везде, потому что каждый экземпляр наследует глобальный, Таким образом, вы можете найти
initLifecycle
,initEvents
,initRender、initState
, И вinitState
Хуки жизненного цикла вызываются туда и обратно соответственно.beforeCreate
иcreated,
Увидев это, я понимаю, почему DOM нельзя манипулировать при создании. Потому что на данный момент реальный элемент DOM еще не отрендерен в документе.created
Это только означает, что инициализация состояния данных завершена.Сосредоточьтесь на initState()initState (vm) {
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)
}
}
Глядя на систему ответа данных Vue через initData, поскольку передаются только данные, выполните initData(vm)
function initData (vm: Component) {
let data = vm.$options.data
data = vm._data = typeof data === 'function'
? getData(data, vm)
: data || {}
const keys = Object.keys(data)
const props = vm.$options.props
const methods = vm.$options.methods
let i = keys.length
while (i--) {
const key = keys[i]
proxy(vm, `_data`, key)
}
observe(data, true /* asRootData */)
}
proxy (target, sourceKey, key) {
sharedPropertyDefinition.get = function proxyGetter () {
return this[sourceKey][key]
}
sharedPropertyDefinition.set = function proxySetter (val) {
this[sourceKey][key] = val
}
Object.defineProperty(target, key, sharedPropertyDefinition)
}
Выньте ключ в данных, выполните цикл через прокси-сервер, вы можете напрямую получить доступ к значению в данных через атрибут this.и проксировать данные в объекте экземпляра, чтобы мы могли получить доступ к data.todos через этот .todos Далее официальная система ответа данных наблюдаем(данные, истина /* asRootData */), данные обрабатываются методом get и устанавливаются через Object.defineProperty, чтобы данные отвечали
class Observer {
constructor(value) {
this.value = value
this.dep = new Dep()
this.vmCount = 0
def(value, '__ob__', this)
if (Array.isArray(value)) {
const augment = hasProto ? protoAugment : copyAugment augment(value, arrayMethods, arrayKeys) this.observeArray(value)
} else {
this.walk(value)
}
}
walk(obj: Object) {
const keys = Object.keys(obj) for (let i = 0; i < keys.length; i++) {
defineReactive(obj, keys[i], obj[keys[i]])
}
}
observeArray(items) {
for (let i = 0, l = items.length; i < l; i++) {
observe(items[i])
}
}
}
существуетObserver
класс, мы используемwalk
Метод вызывается циклически по атрибутам данных datadefineReactive
метод,defineReactive
Метод очень прост, просто преобразуйте атрибуты данных данных в атрибуты доступа и рекурсивно наблюдайте за данными, в противном случае можно наблюдать только прямые податрибуты данных данных. Таким образом, наш первый шаг завершен.Когда мы изменяем или получаем значение атрибута данных, мы передаемget
иset
чтобы получать уведомления.
function defineReactive (
obj,
key,
val,
customSetter,
shallow
) {
const dep = new Dep()
let childOb = !shallow && observe(val)
Object.defineProperty(obj, key, {
enumerable: true,
configurable: true,
get: function reactiveGetter () {
const 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) {
const value = getter ? getter.call(obj) : val
if (newVal === value || (newVal !== newVal && value !== value)) {
return
}
val = newVal
childOb = !shallow && observe(newVal)
dep.notify()
}
})
}
- вlet childOb = !shallow && наблюдать(val), сделать рекурсивный вызов, установить все данные данных, включая подмножества, и ответить
-
Среди них в классе Observe, если свойство является массивом, оно будет преобразовано
if (Array.isArray(value)) { const augment = hasProto ? protoAugment : copyAugment augment(value, arrayMethods, arrayKeys) this.observeArray(value) } else { this.walk(value) }export const arrayMethods = Object.create(arrayProto) ;[ 'push', 'pop', 'shift', 'unshift', 'splice', 'sort', 'reverse' ] .forEach(function (method) { const original = arrayProto[method] def(arrayMethods, method, function mutator (...args) { const result = original.apply(this, args) const ob = this.__ob__ let inserted switch (method) { case 'push': case 'unshift': inserted = args break case 'splice': inserted = args.slice(2) break } if (inserted) ob.observeArray(inserted) // notify change ob.dep.notify() return result }) })
После выполнения initData(vm) соответствующая система завершена.В это время выполняется callHook(vm, 'created') для создания триггера, продолжается возврат к _init() и выполнение к vm.$mount(vm. $options.el)
Vue.prototype._init = function (options) {
const vm= this
vm._uid = uid++
let startTag, endTag
vm._isVue = true
if (options && options._isComponent) {
initInternalComponent(vm, options)
} else {
vm.$options = mergeOptions(
resolveConstructorOptions(vm.constructor),
options || {},
vm
)
}
vm._renderProxy = vm
vm._self = vm
initLifecycle(vm)
initEvents(vm)
initRender(vm)
callHook(vm, 'beforeCreate')
initInjections(vm)
initState(vm)
initProvide(vm)
callHook(vm, 'created')
if (vm.$options.el) {
vm.$mount(vm.$options.el)
}
}
При вводе $mount сначала будет получен смонтированный узел el, а затем сначала будет определено, есть ли какие-либо входящиеrenderметод, я не ищу, чтобы пройти в шаблоне,
Vue.prototype.$mount = function (
el,
hydrating
){
el = el && query(el)
const options = this.$options
if (!options.render) {
let template = options.template
if (template) {
if (typeof template === 'string') {
if (template.charAt(0) === '#') {
template = idToTemplate(template)
}
}
} else if (template.nodeType) {
template = template.innerHTML
} else {
if (process.env.NODE_ENV !== 'production') {
warn('invalid template option:' + template, this)
}
return this
}
} else if (el) {
template = getOuterHTML(el)
}
if (template) {
const { render, staticRenderFns } = compileToFunctions(template, {
shouldDecodeNewlines,
delimiters: options.delimiters,
comments: options.comments
}, this)
options.render = render
options.staticRenderFns = staticRenderFns
if (process.env.NODE_ENV !== 'production' && config.performance && mark) {
mark('compile end')
measure(`${this._name} compile`, 'compile', 'compile end')
}
}
}
return mount.call(this, el, hydrating)
}
С шаблоном через compileToFunctions шаблон компилируется в синтаксическое дерево ast, после статической оптимизации, Наконец, он обрабатывается в функцию рендеринга.Функция рендеринга в этом примере использует with(this), Продвиньте эту сферу, {{ задача.текст }} Таким образом, мы можем использовать атрибут прямо в шаблоне, без этого!Конечно, также можно добавить этот Устранено много путаницы, почему это нельзя добавить в шаблон (рендеринг в реакции должен использовать это), v-for преобразуется в _l, согласно предыдущему Vue.prototype._l = renderList
function() {
with (this) {
return _c('div', {
attrs: {
"id": "app"
}
}, [_c('ol', _l((this.todos), function(todo) {
return _c('li', [_v("\n " + _s(todo.text) + "\n ")])
})), _v(" "), _c('child')], 1)
}
}
- После создания функции рендеринга введите mountComponent,
-
первый звонокфункция перед монтированием,
- Затем выполните vm._watcher = new Watcher(vm, updateComponent, noop)
- Наконец, callHook(vm, 'mounted'), выполнить смонтированный, поэтому он был смонтирован на dom до выполнения монтирования
Итак, суть в том, что vm._watcher = new Watcher(vm, updateComponent, noop)
function mountComponent (
vm,
el,
hydrating
): Component {
vm.$el = el
if (!vm.$options.render) {
vm.$options.render = createEmptyVNode
}
callHook(vm, 'beforeMount')
let updateComponent = () => {
vm._update(vm._render(), hydrating)
}
vm._watcher = new Watcher(vm, updateComponent, noop)
hydrating = false
if (vm.$vnode == null) {
vm._isMounted = true
callHook(vm, 'mounted')
}
return vm
}
Посмотреть код наблюдателя
class Watcher {
constructor (
vm,
expOrFn,
cb,
options
) {
this.vm = vm
vm._watchers.push(this)
if (options) {
this.deep = !!options.deep
this.user = !!options.user
this.lazy = !!options.lazy
this.sync = !!options.sync
} else {
this.deep = this.user = this.lazy = this.sync = false
}
this.cb = cb
this.id = ++uid // uid for batching
this.active = true
this.dirty = this.lazy // for lazy watchers
this.deps = []
this.newDeps = []
this.depIds = new Set()
this.newDepIds = new Set()
this.expression = process.env.NODE_ENV !== 'production'
? expOrFn.toString()
: ''
// parse expression for getter
if (typeof expOrFn === 'function') {
this.getter = expOrFn
} else {
this.getter = parsePath(expOrFn)
}
this.value = this.lazy
? undefined
: this.get()
}
get () {
pushTarget(this)
let value
const vm = this.vm
value = this.getter.call(vm, vm)
return value
}
addDep (dep: Dep) {
const id = dep.id
if (!this.newDepIds.has(id)) {
this.newDepIds.add(id)
this.newDeps.push(dep)
if (!this.depIds.has(id)) {
dep.addSub(this)
}
}
}
evaluate () {
this.value = this.get()
this.dirty = false
}
depend () {
let i = this.deps.length
while (i--) {
this.deps[i].depend()
}
}
}
Выполнить конструктор из-за this.lazy=false, this.value = this.lazy ? неопределенный : это.получить();
Выполните метод get, где pushTarget(this), добавьте статический атрибут this в Dep.target (текущий экземпляр new Watcher() )
function pushTarget (_target) {
if (Dep.target) targetStack.push(Dep.target)
Dep.target = _target
}
get () {
pushTarget(this)
let value
const vm = this.vm
value = this.getter.call(vm, vm)
return value
}
接着执行 this.getter.call(vm, vm)
this.getter就是
updateComponent = () => {
vm._update(vm._render(), hydrating)
}
- Сначала вызовите vm._render()
Vue.prototype._render = function (){
const vm = this
const {
render,
staticRenderFns,
_parentVnode
} = vm.$options
let vnode = render.call(vm._renderProxy, vm.$createElement)
return vnode
}
Функция рендеринга, скомпилированная до начала выполнения.При выполнении функции рендеринга путем получения атрибута todos и т. д.
function() {
with (this) {
return _c('div', {
attrs: {
"id": "app"
}
}, [_c('ol', _l((this.todos), function(todo) {
return _c('li', [_v("\n " + _s(todo.text) + "\n ")])
})), _v(" "), _c('child')], 1)
}
}
Метод get, на этот раз Dep.target, уже имеет статическое свойство, экземпляр Watcher.
Таким образом, соответствующий экземпляр dep будет собирать соответствующий экземпляр Watcher.
Вернуться к vnode после выполнения,
updateComponent = () => {
vm._update(vm._render(), hydrating)
}
其中vm._render()执行render函数返回vnode作为参数
接下来执行vm._update
这是首次渲染,所以执行
vm.$el = vm.__patch__(
vm.$el, vnode, hydrating, false,
vm.$options._parentElm,
vm.$options._refElm
)
Vue.prototype._update = function (vnode, hydrating) {
const vm: Component = this
if (vm._isMounted) {
callHook(vm, 'beforeUpdate')
}
const prevEl = vm.$el
const prevVnode = vm._vnode
const prevActiveInstance = activeInstance
activeInstance = vm
vm._vnode = vnode
if (!prevVnode) {
vm.$el = vm.__patch__(
vm.$el, vnode, hydrating, false,
vm.$options._parentElm,
vm.$options._refElm
)
vm.$options._parentElm = vm.$options._refElm = null
} else {
vm.$el = vm.__patch__(prevVnode, vnode)
}
activeInstance = prevActiveInstance
if (prevEl) {
prevEl.__vue__ = null
}
if (vm.$el) {
vm.$el.__vue__ = vm
}
if (vm.$vnode && vm.$parent && vm.$vnode === vm.$parent._vnode) {
vm.$parent.$el = vm.$el
}
}
vm.__patch__( vm.$el, vnode, увлажняющий, ложный, vm.$options._parentElm, vm.$options._refElm ) В соответствии с деревом во vnode создайте соответствующий элемент, вставьте его в родительский узел и создайте все дочерние узлы, рекурсивно перебирая vnode. Вставить в родительский узел Если дочерний элемент является компонентом, таким как Child в этом примере, будет создан и выполнен экземпляр соответствующего VueComponent. Тот же процесс, что и для нового Vue()
если еще неprevVnode
Описание — это первый рендеринг, а реальный DOM создается напрямую. Если у вас уже естьprevVnode
В описании не первый рендер, значит пользуюсьpatch
Алгоритм выполняет необходимые манипуляции с DOM. Это логика обновления DOM Vue. Просто мы не реализовали виртуальный DOM внутри.
При изменении значения свойства срабатывает метод set соответствующего свойства.Поскольку get срабатывает, когда рендер выполняется до этого и собирается соответствующий Watcher, set срабатывает при изменении значения, а ранее собранный Экземпляр Watcher уведомляется о выполнении, и метод рендеринга пересчитывается.
Наконец украсть картинку:
Давно пишу, но реально больше не могу писать.Если у меня в будущем будет хороший язык, давайте разбираться!