В последнее время я участвовал во многих интервью, и почти в каждом интервью будут задавать вопросы об исходном коде Vue. Я открою здесь серию столбцов, чтобы обобщить опыт в этой области.Если вы считаете, что это полезно для вас, пожалуйста, поставьте лайк и поддержите это.
предисловие
Почему вы спрашиваете исходный код Vue на собеседовании?Многие обычно задают этот вопрос.Исходный код Vue редко используется в обычной работе, а API Vue часто используется в работе. Если вы можете использовать Vue API, вы можете удовлетворить потребности работы. Разве это не собеседование для постройки ракеты и работа по завинчиванию гаек? На самом деле, я действительно не хочу спрашивать вас об исходном коде Vue, а просто использую исходный код Vue, чтобы оценить, надежна ли ваша основа JavaScript, например, владение рабочим механизмом JS, область действия, замыкание, цепочка прототипов, рекурсия, каррирование и другие знания. Для того, чтобы отличить уровень способностей интервьюируемого. так вВажно освоить исходный код фреймворка на собеседовании старших фронтенд-инженеров..
1. Как читать исходный код
Есть определенные навыки в чтении исходного кода.Если вы возьмете исходный код и прочитаете его напрямую, гарантировано, что вы сдадитесь, не успев начать.
1. Возьмитесь за основную линию, временно игнорируя ответвление
Рим не строился за один день, и код не писался за день. Любой код разрабатывается по сценарию приложения, и по мере того, как сценарий приложения постоянно добавляется, код постоянно совершенствуется. Например, чтобы понять, как шаблоны и данные рендерятся в окончательный DOM в Vue.Прежде всего, нам нужно написать простейшую демонстрацию, на основе этой заданной сцены для изучения.. В процессе исследования код, относящийся к заданной сцене, является основной линией, а код, не относящийся к заданной сцене, — ответвлением.
Демонстрационный код выглядит следующим образом
<!DOCTYPE html>
<html>
<head>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
</head>
<body>
<div id="app">
<p>{{aa}}<span>{{bb}}</span></p>
</div>
</body>
<script>
var app = new Vue({
el: '#app',
data(){
return{
aa:'欢迎',
bb:'Vue'
}
}
})
</script>
</html>
2. Используйте инструменты разработчика Chrome для отладки точек останова
Отладка точки останова — это навык, которым должен овладеть старший фронтенд-инженер. В этой статье используется отладка точки останова для чтения исходного кода Vue и рассказывается, как шаблоны и данные отображаются в окончательной DOM в Vue.
3. Логическая схема
Давайте начнем с предыдущей логической блок-схемы, Далее будет показано, как шаблон и данные отображаются в окончательной модели DOM в Vue в соответствии с этой диаграммой.
2. новый Вью()
Чтобы использовать Vue, сначалаnew Vue()
, то Vue — это конструктор класса.
function Vue(options) {
if (!(this instanceof Vue)) {
warn('Vue is a constructor and should be called with the `new` keyword');
}
this._init(options);
}
Вы можете видеть, что Vue можно вызывать только с ключевым словом new.Если он не вызывается с новым, в консоли будет напечатано предупреждение, означающее, что Vue является конструктором и должен вызываться с ключевым словом «new».
воплощать в жизньthis._init(options)
, чтобы инициализировать Vue,Сделайте здесь точку останова и нажмите F11, чтобы войтиthis._init
метод выполнен.
Три, это ._инит
Vue.prototype._init = function(options) {
var vm = this;
if (options && options._isComponent) {
//...
} else {
vm.$options = mergeOptions(
resolveConstructorOptions(vm.constructor),
options || {},
vm
);
}
initProxy(vm);
initRender(vm);
initState(vm);
if (vm.$options.el) {
vm.$mount(vm.$options.el);
}
}
воплощать в жизньmergeOptions
сливатьсяoptions
и различные инициализации в заданной сценеvm.$options.el
ценность#app
, поэтому выполнитеvm.$mount(vm.$options.el)
,передачаvm.$mount
Метод монтирует экземпляр в DOM, а затем анализирует процесс монтирования Vue.
существуетvm.$mount(vm.$options.el)
Сделайте здесь точку останова и нажмите F11, чтобы войтиvm.$mount
метод выполнен.
1. Параметры слияния
// 合并options
if (options && options._isComponent) {
} else {
vm.$options = mergeOptions(
resolveConstructorOptions(vm.constructor),
options || {},
vm
);
}
вoptions._isComponent
дляtrue
В этом смысл компонента, и понятно, что сеттинг не является компонентом, поэтомуfalse
,идтиelse
часть кода.
оставить это в покоеmergeOptions
внутренняя логика, только помните, что после слияния можно использоватьvm.$options
доступ черезnew Vue(options)
входящие параметрыoptions
Просто сделай это.
2. инициализация прокси
function initProxy(vm) {
if (hasProxy) {
var options = vm.$options;
var handlers = options.render && options.render._withStripped ?
getHandler : hasHandler;
vm._renderProxy = new Proxy(vm, handlers);
} else {
vm._renderProxy = vm;
}
}
Пучокvm
Используйте прокси, чтобы сделать прокси для достижения черезvm
При доступе к несуществующим данным будет выдано сообщение об ошибке, подробное объяснение которого здесь не приводится.
Помните здесь,vm
После выполнения прокси назначьте егоvm._renderProxy
,доступvm._renderProxy
довольно доступныйvm
, который впоследствии генерируетсяvnode
(Виртуальный DOM).
3. initRender
function initRender(vm) {
vm._vnode = null;
vm._c = function(a, b, c, d) {
return createElement(vm, a, b, c, d, false);
};
vm.$createElement = function(a, b, c, d) {
return createElement(vm, a, b, c, d, true);
};
}
Вспомним здесь, что определениеvm._c
а такжеvm.$createElement
Два метода, сгенерированные в последующемvnode
Полезно во время.
4. состояние инициализации()
function initState(vm) {
vm._watchers = [];
var 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
,watch
, в сет-сцене просто понять инициализациюdata
процесс
на съемочной площадкеopts.data
существует, поэтому выполнитеinitData(vm)
.
function initData(vm) {
var data = vm.$options.data;
data = vm._data = typeof data === 'function' ?
getData(data, vm) :
data || {};
if (!isPlainObject(data)) {
data = {};
warn(
'data functions should return an object:\n' +
'https://vuejs.org/v2/guide/components.html#data-Must-Be-a-Function',
vm
);
}
var keys = Object.keys(data);
var props = vm.$options.props;
var methods = vm.$options.methods;
var i = keys.length;
while (i--) {
var key = keys[i]; {
if (methods && hasOwn(methods, key)) {
warn(//..);
}
}
if (props && hasOwn(props, key)) {
warn(//...);
} else if (!isReserved(key)) {
proxy(vm, "_data", key);
}
}
observe(data, true /* asRootData */ );
}
function getData(data, vm) {
pushTarget();
try {
return data.call(vm, vm)
} catch (e) {
handleError(e, vm, "data()");
return {}
} finally {
popTarget();
}
}
function 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);
}
new Vue({
el: '#app',
data() {
return {
aa: '欢迎',
bb: 'Vue'
}
}
})
существуетinitData(vm)
в, получитьvm.$options
Атрибут DATA также является опцией Vue, если Data является функцией, сgetData
метод, чтобы получить возвращаемое значение и присвоить егоvm._data
, опять такиvm._data
, является ли это объектом и значение ключа не может совпадать с параметромprops
, опцииmethods
Ключ тот же суд.
затем используйтеisReserved
Метод параvm._data
данные фильтруются, а не$
или_
начать сproxy
метод в качестве прокси для него, в котором используетсяObject.defineProperty
Перехват данных, таких как доступvm.aa
, на самом деле доступvm._data.aa
После агента посетитеvm
может получить доступvm._data
ценность .
Вот почему его можно использоватьvm.aa
доступ к определенным в опции данныхaa
, имейте это в виду при созданииvnode
(Виртуальный DOM) полезен в процессе.
4. vm.$mount
const mount = Vue.prototype.$mount;
Vue.prototype.$mount = function(el, hydrating) {
el = el && query(el);
if (el === document.body || el === document.documentElement) {
warn("Do not mount Vue to <html> or <body> - mount to normal elements instead.");
return this
}
var options = this.$options;
if (!options.render) {
var template = options.template;
if (template) {
//...
} else if (el) {
template = getOuterHTML(el);
}
}
if (template) {
var ref = compileToFunctions(template, {
outputSourceRange: "development" !== 'production',
shouldDecodeNewlines: shouldDecodeNewlines,
shouldDecodeNewlinesForHref: shouldDecodeNewlinesForHref,
delimiters: options.delimiters,
comments: options.comments
}, this);
var render = ref.render;
var staticRenderFns = ref.staticRenderFns;
options.render = render;
options.staticRenderFns = staticRenderFns;
}
return mount.call(this, el, hydrating)
}
почему первыйVue.prototype.$mount
назначить наmount
, а затем переопределитьVue.prototype.$mount
. Потому что$mount
Методы зависят от платформы и сборки. при разных обстоятельствах$mount
Методы разные. Итак, чтобы кэшировать исходный прототип$mount
метод, определяющий различные$mount
метод и, наконец, вызвать исходный прототип$mount
метод.Эта технология называется перехватом функций..
1. скомпилировать функции
if (template) {
var ref = compileToFunctions(template, {
outputSourceRange: "development" !== 'production',
shouldDecodeNewlines: shouldDecodeNewlines,
shouldDecodeNewlinesForHref: shouldDecodeNewlinesForHref,
delimiters: options.delimiters,
comments: options.comments
}, this);
var render = ref.render;
options.render = render;
}
Не определено в заданной сценеrender
метод, который требуется в Vuerender
метод созданияvnode
(Виртуальный DOM), поэтому используйтеcompileToFunctions
метод созданияrender
метод и смонтировать вvm.$options
.
а такжеcompileToFunctions
метод, используйте шаблонtemplate
, в заданной сцене шаблон не определенtemplate
определяется только цель монтированияel
.那么要先通过el
генерироватьtemplate
.
el = el && query(el);
if (el === document.body || el === document.documentElement) {
warn("Do not mount Vue to <html> or <body> - mount to normal elements instead.");
return this
}
var options = this.$options;
if (!options.render) {
var template = options.template;
if (template) {
//...
} else if (el) {
template = getOuterHTML(el);
}
}
использоватьquery
метод полученияel
Затем соответствующий объект DOM присваиваетсяel
,правильноel
После принятия решения Vue не может быть смонтирован на корневых узлах, таких как body и html.
this.$options.template
не существует, поэтому выполнитеtemplate = getOuterHTML(el)
,template
ценностьel
Соответствующий HTML-контент.
2. компонент монтирования
существуетvm.$mount
последнее исполнениеreturn mount.call(this, el, hydrating)
, который вызывает$mount
метод,Сделайте здесь точку останова, нажмите F11, чтобы войти в исходный прототип$mount
метод.
Vue.prototype.$mount = function(el,hydrating) {
el = el && inBrowser ? query(el) : undefined;
return mountComponent(this, el, hydrating)
};
на оригинальном прототипе$mount
метод, последний вызовmountComponent
метод, параметрhydrating
Связанный с рендерингом на стороне сервера, в среде браузераfalse
, можно игнорировать.
return mountComponent(this, el, hydrating)
Сделайте здесь точку останова и нажмите F11, чтобы войтиmountComponent
метод.
function mountComponent(vm,el,hydrating) {
vm.$el = el;
var updateComponent;
updateComponent = function() {
vm._update(vm._render(), hydrating);
};
new Watcher(vm, updateComponent, noop, {
before: function before() {
if (vm._isMounted && !vm._isDestroyed) {
callHook(vm, 'beforeUpdate');
}
}
}, true /* isRenderWatcher */ );
hydrating = false;
if (vm.$vnode == null) {
vm._isMounted = true;
callHook(vm, 'mounted');
}
return vm
}
В настоящее времяel
объект DOM цели монтирования, назначенныйvm.$el
,Запомнить здесьvm.$el
является объектом DOM цели монтирования экземпляра Vue..
Создайте экземпляр Watcher рендеринга, вторым параметром которого является значение функции обратного вызова. Здесь есть две функции: одна для выполнения функции обратного вызова во время инициализации, а другая — для выполнения функции обратного вызова при изменении отслеживаемых данных в экземпляре vm.
Функция обратного вызоваupdateComponent
функция, которая выполняет функцию обратного вызова, а не выполняетvm._update(vm._render(), hydrating)
, вvm._render()
Как параметр он выполняется первым,vm._update(vm._render(), hydrating)
Сделайте точку останова в методе и нажмите F11, чтобы войтиvm._render()
метод.
Пять, VM._Render.
vm._render
Основная функция – генерироватьvnode
(Дерево виртуального DOM) и вернуться.
Vue.prototype._render = function(){
var vm = this;
var ref = vm.$options;
var render = ref.render;
var _parentVnode = ref._parentVnode;
if (_parentVnode) {
//...
}
vm.$vnode = _parentVnode;
var vnode;
try{
vnode = render.call(vm._renderProxy, vm.$createElement);
}catch(e){
//...
}
return vnode
}
vm.$vnode
Представляет родительский виртуальный DOM экземпляра Vue в заданной сцене,_parentVnode
не определено, поэтомуvm.$vnode
не определено.
render
метод находится вvm.$mount
прошедшийcompileToFunctions
сгенерированный метод, код выглядит следующим образом.
(function anonymous() {
with(this) {
return _c('div', {
attrs: {
"id": "app"
}
}, [_c('p', [_v(_s(aa)), _c('span', [_v(_s(bb))])])])
}
})
Роль оператора with заключается в том, чтобы указать объект по умолчанию для оператора или группы операторов, напримерwith(this){ a + b }
эквивалентthis.a + this.b
.
потому чтоrender
черезcall(vm._renderProxy, vm.$createElement)
звонить, такthis
даvm._renderProxy
, представленный ранееinitProxy
серединаvm._renderProxy
эквивалентvm
, так вотthis
то естьvm
.
Такrender
Метод также эквивалентен следующему коду
function (){
return vm._c('div', {
attrs: {
"id": "app"
}
}, [vm._c('p',
[
vm._v(vm._s(vm.aa)),
vm._c('span', [vm._v(this._s(vm.bb))])
]
)]
)
}
vm._c
введенный ранееinitRender
определено в , а также определеноvm.$createElement
, не используется в заданной сцене.
vm._c = function(a, b, c, d) { return createElement(vm, a, b, c, d, false) }
vm._c
а такжеvm.$createElement
внутренние звонкиcreateElement
метод, единственная разница между ними заключается в том, что последний параметрtrue
илиfalse
;
vm._c
Он используется для метода рендеринга, скомпилированного в шаблон,vm.$createElement
Он используется написанным пользователем методом рендеринга.
const vm = new Vue({
el:'#app',
render: h => h(App)
})
в приведенном выше кодеh
то естьvm.$createElement
.
vnode = render.call(vm._renderProxy, vm.$createElement)
, сделайте здесь точку останова, дважды нажмите F11, чтобы ввестиcreateElement
метод.
var SIMPLE_NORMALIZE = 1;
var ALWAYS_NORMALIZE = 2;
function createElement(context, tag, data, children, normalizationType, alwaysNormalize) {
if (Array.isArray(data) || isPrimitive(data)) {
normalizationType = children;
children = data;
data = undefined;
}
if (isTrue(alwaysNormalize)) {
normalizationType = ALWAYS_NORMALIZE;
}
return _createElement(context, tag, data, children, normalizationType);
}
- параметр
context
: Объект контекста, то есть это; - параметр
tag
: имя тега HTML, объект опций компонента; - параметр
data
: объект данных узла; - параметр
children
: дочерние виртуальные узлы (VNodes), также строки могут использоваться для создания «текстовых виртуальных узлов». - параметр
normalizationType
alwaysNormalize
: контролирует, каким образом обрабатыватьchildren
, установленная сцена не используется, игнорируйте ее.
if (Array.isArray(data) || isPrimitive(data)) {
normalizationType = children;
children = data;
data = undefined;
}
Обработайте проблему совместимости при передаче параметров и оцените второй параметрdata
Является ли это массивом или базовым типом, если да, то указывает второй параметрdata
должен быть третьим параметромchildren
, так будетchildren
назначить наnormalizationType
, а потомdata
назначить наchildren
, а потомdata
установлен вundefined
.
последний звонок_createElement
метод,Сделайте здесь точку останова и нажмите F11, чтобы войти_createElement
метод.
1. _createElement
function _createElement(context, tag, data, children, normalizationType){
var vnode;
if (typeof tag === 'string') {
if (config.isReservedTag(tag)) {
vnode = new VNode(config.parsePlatformTagName(tag), data,
children, undefined, undefined, context);
}
}
return vnode
}
Игнорировать предыдущую логику суждения для ряда параметров, и посколькуvm._c
Последний параметр методаflase
,такnormalizationType
дляundefined
, не будет заниматьсяchildren
, посмотрите непосредственно на основную логику.
В заданной сцене,tag
это строка, послеconfig.isReservedTag(tag)
судитьtag
является зарезервированным символом HTML, выполнитьnew VNode(config.parsePlatformTagName(tag), data, children, undefined, undefined, context)
Создайте экземпляр VNode для созданияvnode
2. новый виртуальный узел ()
new VNode()
Роль заключается в создании экземпляра VNode для созданияvnode
, который является виртуальным DOM. Краткое введение в виртуальный DOM.Объекты DOM в браузере очень большие.Например, вы можете увидеть, что объекты DOM очень большие, когда нажимаете Enter на консоли. Стандарты браузера делают объекты DOM очень сложными. Поэтому, когда мы часто манипулируем объектами DOM, возникают определенные проблемы с производительностью. Хотя виртуальный DOM использует собственный JS-объект для описания объекта DOM, он не содержит методов для управления DOM, поэтому стоимость управления им намного меньше, чем управление объектами DOM.
const div = document.createElement('div');
let str= ''
for(let key in div){
str += key + ';'
}
function VNode(tag, data, children, text, elm, context, componentOptions, asyncFactory) {
this.tag = tag;
this.data = data;
this.children = children;
this.text = text;
this.elm = elm;
this.context = context;
this.key = data && data.key;
this.parent = undefined;
}
В заданной сцене просто обратите внимание на эти свойства в конструкторе VNode и сначала игнорируйте остальные.
-
tag
: название ярлыка -
data
:данные -
children
: дочерний узел -
text
: текст текущего узла -
elm
: соответствующий реальный объект DOM -
parent
: родительский узел
В установленной сцене сгенерированный объект DOM, как показано на рисунке ниже, представляет собой древовидную структуру, тогда соответствующий виртуальный DOM также должен быть древовидной структурой, затем следуйтеVNode
третий параметр функцииchildren
Связанный,children
через_createElement
четвертый параметр функцииchildren
пройти мимо,_createElement
фактически сгенерированныйrender
вызывается метод, возвращаемся к сгенерированномуrender
метод (кrender
метод трактовался эквивалентно).
function (){
return vm._c('div', {
attrs: {
"id": "app"
}
}, [vm._c('p',
[
vm._v(vm._s(vm.aa)),
vm._c('span', [vm._v(this._s(vm.bb))])
]
)]
)
}
При выполнении функции, если параметр является функцией, он должен быть выполнен первым.
-
воплощать в жизнь
vm._c('div'..
когда встречается параметр[vm._c('p', [vm._v(vm._s(vm.aa)), vm._c('span', [vm._v(vm._s(vm.bb))])])]
; -
воплощать в жизнь
vm._c('p', [vm._v(vm._s(vm.aa)), vm._c('span', [vm._v(vm._s(vm.bb))])])
, параметры встречи[vm._v(vm._s(vm.aa)), vm._c('span', [vm._v(vm._s(vm.bb))])]
; -
воплощать в жизнь
vm._v(vm._s(vm.aa))
а такжеvm._c('span', [vm._v(vm._s(vm.bb))])
, встретив параметрvm._s(vm.aa)
а также[vm._v(vm._s(vm.bb))]
-
воплощать в жизнь
vm._s(vm.aa)
а такжеvm._v(vm._s(vm.bb))
, параметры встречиvm._s(vm.bb)
-
воплощать в жизнь
vm._s(vm.bb)
Согласно вышеприведенному анализу, первое, что нужно сделать, этоvm._s(vm.aa)
а такжеvm._s(vm.bb)
, затем выполнитеvm._v(vm._s(vm.bb))
.
существуетinstallRenderHelpers
определяется в функцииvm._v
а такжеvm._s
. Когда Vue.js загружен, он выполняетrenderMixin(Vue)
, при котором выполнениеinstallRenderHelpers(Vue.prototype)
,Vue.prototype
то естьvm
. такvm._v
соответствуетcreateTextVNode
метод,vm._s
соответствуетtoString
метод.
function renderMixin (Vue) {
installRenderHelpers(Vue.prototype)
}
function installRenderHelpers(target) {
target._s = toString;
target._v = createTextVNode;
}
function createTextVNode (val) {
return new VNode(undefined, undefined, undefined, String(val))
}
function toString (val) {
return val == null ? '' :
Array.isArray(val) || (isPlainObject(val) && val.toString === _toString)
? JSON.stringify(val, null, 2) : String(val)
}
-
createTextVNode
Метод используется для создания виртуального DOM текстовых узлов, обратите внимание, что его параметры передаются вnew VNode
четвертый параметрtext
(текст текущего узла). -
toString
метод используется для преобразования любого значения в строку.
затем выполнитьvm._v(vm._s(vm.bb))
получить одинvnode
, обозначаемый как vnode1, а затем выполнитьvm._c('span', [vm._v(vm._s(vm.bb))])
, который выполняет довольноvm._c('span', [vnode])
, возьми другойvnode
, обозначаемый как vnode2, в это время значение vnode1children
Стоимость свойства[vnode2]
.
И так далее, послойное выполнение. при исполненииvm._c('div'..
Вы получаете виртуальное дерево DOM, как показано на рисунке.
существуетvm._render
Создайте виртуальное дерево DOM из шаблона и данных, а затемvm._update
Превратите виртуальное дерево DOM в настоящее дерево DOM.
6. vm._update
Vue.prototype._update = function(vnode, hydrating) {
var vm = this;
var 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);
}
};
воплощать в жизньvar prevVnode = vm._vnode
,vm._vnode
Это виртуальный DOM, сгенерированный текущим экземпляром Vue. Это первый рендеринг в заданной сцене.vm._vnode
не определено, поэтомуprevVnode
Для undefined выполнить сноваvm._vnode = vnode
, назначьте Virtual DOM, сгенерированный текущим экземпляром Vue, наvm._vnode
.
потому чтоprevVnode
не определено, поэтому выполнитеvm.$el = vm.__patch__(vm.$el, vnode, hydrating, false)
, Введите первую операцию рендеринга DOM.
Vue.prototype.__patch__ = inBrowser ? patch : noop;
В среде браузераVue.prototype.__patch__
даpatch
.
1, патч
var patch = createPatchFunction({ nodeOps: nodeOps, modules: modules });
patch
Зависит отcreatePatchFunction
генерировать. его параметрыnodeOps
Метод, который инкапсулирует ряд операций DOM, параметрыmodules
Это функция-ловушка, которая определяет некоторые модули, я не буду здесь ее подробно представлять, давайте посмотрим.createPatchFunction
реализация.
export function createPatchFunction(backend) {
const {modules,nodeOps} = backend
return function patch(oldVnode, vnode, hydrating, removeOnly) {
//...
}
}
Перед этим подумайте, почемуcreatePatchFunction
создатьpatch
метод.
потому чтоpatch
зависит от контекста, как в веб-контексте, так и в контексте Weex, каждый имеет свой собственныйnodeOps
а такжеmodules
.但是不同环境的patch
Основная логика та же, а дифференцированную часть нужно только различать по параметрам, здесь используется прием каррирования функций.createPatchFunction
Заранее закрепите дифференцированные параметры, не звоня каждый разpatch
при прохождении соответствующегоnodeOps
а такжеmodules
, эту технику программирования стоит изучить.
затем выполнитьvm.$el = vm.__patch__(vm.$el, vnode, hydrating, false)
, в конце концов звонитpatch
метод,Сделайте здесь точку останова и нажмите F11, чтобы войтиpatch
метод.
function patch(oldVnode, vnode, hydrating, removeOnly) {
const insertedVnodeQueue = []
if (isUndef(oldVnode)) {} else {
const isRealElement = isDef(oldVnode.nodeType)
if (!isRealElement && sameVnode(oldVnode, vnode)) {} else {
if (isRealElement) {
oldVnode = emptyNodeAt(oldVnode)
}
const oldElm = oldVnode.elm
const parentElm = nodeOps.parentNode(oldElm)
createElm(vnode, insertedVnodeQueue, parentElm, nodeOps.nextSibling(oldElm))
if (isDef(parentElm)) {
removeVnodes(parentElm, [oldVnode], 0, 0)
} else if (isDef(oldVnode.tag)) {}
}
}
return vnode.elm
}
- параметр
oldVnode
: Последний виртуальный DOM, значение в заданной сцене равноvm.$el
является DOM-объектом; - параметр
vnode
: На этот раз виртуальный DOM; - параметр
hydrating
: в случае несерверного рендерингаfalse
,Можно игнорировать; - параметр
removeOnly
: вtransition-group
Он используется в сцене, если его нет в сцене настройки, онfalse
, Можно игнорировать.
еслиoldVnode
Не виртуальный DOM, а объект DOM, поместитеoldVnode
использоватьemptyNodeAt
в виртуальный DOM и в его свойствахelm
присвоение преобразованному объекту DOM, поэтомуoldElm
эквивалентvm.$el
,С использованиемnodeOps.parentNode(oldElm)
ПолучатьoldElm
Родительский узел DOM элемента , в данном случае body.
function emptyNodeAt (elm) {
return new VNode(nodeOps.tagName(elm).toLowerCase(), {}, [], undefined, elm)
}
//nodeOps.parentNode
function parentNode (node) {
return node.parentNode
}
воплощать в жизньcreateElm(vnode, insertedVnodeQueue, parentElm, nodeOps.nextSibling(oldElm))
генерировать настоящий DOM,Сделайте здесь точку останова и нажмите F11, чтобы войтиcreateElm
метод,nodeOps.nextSibling(oldElm)
ВыбиратьoldElm
следующий брат .
2. создатьвяз
function createElm(vnode, insertedVnodeQueue, parentElm, refElm, nested, ownerArray, index) {
vnode.isRootInsert = !nested;
var data = vnode.data;
var children = vnode.children;
var tag = vnode.tag;
if (isDef(tag)) {
vnode.elm = nodeOps.createElement(tag, vnode);
createChildren(vnode, children, insertedVnodeQueue);
if (isDef(data)) {
invokeCreateHooks(vnode, insertedVnodeQueue);
}
insert(parentElm, vnode.elm, refElm);
} else if (isTrue(vnode.isComment)) {
} else {
vnode.elm = nodeOps.createTextNode(vnode.text);
insert(parentElm, vnode.elm, refElm);
}
}
- параметр
vnode
: виртуальный дом; - параметр
insertedVnodeQueue
: Перехват очереди функций; - параметр
parentElm
: параметрvnode
Объект DOM родительского узла, соответствующий реальному объекту DOM; - параметр
refElm
: объект узла-заполнителя, например, параметрыvnode
Соответствует следующему родственному узлу объекта DOM; - параметр
nested
:судитьvnode
Является ли это виртуальным DOM корневого экземпляра; - параметр
ownerArray
: массив собственных дочерних узлов - параметр
index
: Нижний индекс массива дочерних узлов
параметрownerArray
,index
это решитьvnode
Он был отрендерен раньше, и теперь как новый виртуальный DOM, ошибка, вызванная перезаписью его вяза, установленной сцены, является первым рендерингом, который можно игнорировать.
-
vnode.tag
Если он существует, выполнитеnodeOps.createElement(tag, vnode)
Создайте реальный узел DOM и назначьте егоvnode.elm
,nodeOps.createElement
соответствуетcreateElement
метод.
function createElement (tagName, vnode) {
var elm = document.createElement(tagName);
if (tagName !== 'select') {
return elm
}
if (vnode.data && vnode.data.attrs && vnode.data.attrs.multiple !== undefined) {
elm.setAttribute('multiple', 'multiple');
}
return elm
}
-
vnode.tag
Если он не существует, это, вероятно, комментарий или простой текстовый узел, выполнитеnodeOps.createTextNode(vnode.text)
Создайте комментарий или простой текстовый узел и назначьте егоvnode.elm
,nodeOps.createTextNode
соответствуетcreateTextNode
метод.
function createTextNode (text) {
return document.createTextNode(text)
}
createChildren(vnode, children, insertedVnodeQueue)
используется для обработкиvnode
дочерний виртуальный дом,Сделайте здесь точку останова и нажмите F11, чтобы войтиcreateChildren
метод
3. создать детей
function createChildren(vnode, children, insertedVnodeQueue) {
if (Array.isArray(children)) {
checkDuplicateKeys(children);
for (var i = 0; i < children.length; ++i) {
createElm(children[i], insertedVnodeQueue, vnode.elm, null, true, children, i);
}
} else if (isPrimitive(vnode.text)) {
nodeOps.appendChild(vnode.elm, nodeOps.createTextNode(String(vnode.text)));
}
}
createChildren
Логика очень проста, на самом деле это обходvnode
Дочерний виртуальный DOM, рекурсивно называемыйcreateElm
, который является широко используемым алгоритмом обхода в глубину.vnode.elm
какchildren[i]
(Виртуальный DOM) Передается родительский узел, соответствующий реальному DOM.
когдаchildren
когда не массив. судитьvnode.text
Является ли он базовым типом, если он называетсяnodeOps.createTextNode
Создайте простой текстовый узел, затем вызовитеnodeOps.appendChild
вставить вvnode.elm
середина.
4. вставить
воплощать в жизньinsert(parentElm, vnode.elm, refElm)
поместите полученный DOM (vnode.elm
) вставляется в соответствующий родительский узел (parentElm
), так как это рекурсивный вызов, дочерний виртуальный DOM будет вызываться первым.insert
, поэтому порядок вставки всего дерева Virtual DOM после создания реального DOM сначала дочерний, а затем родительский.
существуетinsert(parentElm, vnode.elm, refElm)
Нажмите точку останова и нажмите F11, чтобы войтиinsert
метод.
function insert(parent, elm, ref$$1) {
if (isDef(parent)) {
if (isDef(ref$$1)) {
if (nodeOps.parentNode(ref$$1) === parent) {
nodeOps.insertBefore(parent, elm, ref$$1);
}
} else {
nodeOps.appendChild(parent, elm);
}
}
}
- параметр
parent
: родительский узел узла, который нужно вставить - параметр
elm
: вставить узел - параметр
ref$$1
: Ссылочный узел, будет вставлен перед ссылочным узлом.
nodeOps.insertBefore
вести перепискуinsertBefore
метод,nodeOps.appendChild
вести перепискуappendChild
метод,
function insertBefore (parentNode, newNode, referenceNode) {
parentNode.insertBefore(newNode, referenceNode);
}
function appendChild (node, child) {
node.appendChild(child);
}
insertBefore
Методы иappendChild
На самом деле метод заключается в вызове API собственного DOM для выполнения операций DOM.
5. Вернитесь к созданию Elm
при звонкеcreateChildren
,Пучокvnode
После обхода дочернего виртуального DOM вернитесь к исходному вызовуcreateElm
метод, затем выполнитеinsert(parentElm, vnode.elm, refElm)
, его параметрparentElm
Значение находится вpatch
выполнение метода
const oldElm = oldVnode.elm
const parentElm = nodeOps.parentNode(oldElm)
приобретенный, на съемочной площадкеoldElm
для<div id="app"><p>{{aa}}<span>{{bb}}</span></p></div>
этот объект DOM, поэтомуparentElm
для объекта тела.vnode.elm
Создайте соответствующий реальный DOM для дерева Virtual DOM.
Фактически весь процесс представляет собой рекурсивный вызовcreateElm
Настоящее DOM-дерево создается и вставляется в тело.
Увидев это, возможно, вы вдруг поймете, что Vue — это такой динамически создаваемый DOM.
Также вinvokeCreateHooks
Метод выполнит ряд функций-ловушек, таких какdata
серединаattrs: {id: "app"}
Как атрибуты добавляются к тегу div, здесь не рассматривается.
if (isDef(parentElm)) {
removeVnodes([oldVnode], 0, 0);
} else if (isDef(oldVnode.tag)) {
}
потому чтоparentElm
существуют, вpatch
последний вызов методаremoveVnodes
поставить оригинальный шаблон<div id="app"><p>{{aa}}<span>{{bb}}</span></p></div>
удален из своего родительского узла. Это также подтверждает, почему цель монтирования экземпляра Vue должна быть ограничена, и она не может быть смонтирована на теле или html-объекте.
назадvm._updata
метод, потому чтоpatch
Метод вернет объект DOM новой текущей цели монтирования экземпляра Vue, поэтому обновите его.vm.$el
.
vm.$el = vm.__patch__(vm.$el, vnode, hydrating, false /* removeOnly */);
6. Вернитесь к mountComponent
if (vm.$vnode == null) {
vm._isMounted = true;
callHook(vm, 'mounted');
}
return vm
vm.$vnode
Представляет родительский виртуальный DOM экземпляра Vue. Если он равен null, это означает, что в данный момент это корневой экземпляр Vue.vm._isMounted 为 true
, указывая на то, что экземпляр смонтирован и выполняется в то же времяmounted
функция крючка.
7. Резюме
Ключевым процессом рендеринга шаблонов и данных в конечный дом можно обобщить в одном предложении:
существуетvm._render
Создайте виртуальное дерево DOM от дочернего к родительскому вvm._update
Реальный DOM генерируется от родителя к дочернему, а затем от дочернего к родительскому, сгенерированный реальный DOM вставляется в соответствующий родительский узел, создается реальное дерево DOM и, наконец, вставляется в тело.