Если есть какие-либо ошибки, я надеюсь, что вы оставите сообщение и дадите указатели, и вы будете очень счастливы.
Немного сумбурно, разные методы переплетаются, сложно разобраться в порядке, пожалуйста Хайхан
фронт потока
существуетVue
В исходном коде Youda принялFlow
В качестве проверки статического типаFlow
даfacebook
Производится инструментом статической проверки типов.
зачем использоватьFlow
?
Как мы все знаем,JavaScript
является слабо типизированным языком.
Так называемый слабый тип означает, что при определении переменных тип не требуется, а тип будет определяться автоматически в процессе работы программы.Если язык может неявно преобразовывать все свои типы, то и свои переменные, выражения и т.д. участвовать в операции.Даже если тип неверен, правильный тип может быть получен неявным преобразованием, которое для пользователя, по-видимому, может выполнять все операции над всеми типами, поэтомуJavascript
называется слабой типизацией.
Возможно, в первые дни эту функцию иногда было очень удобно использовать, но когда вы будете работать над более крупным проектом, вы обнаружите, что эта функция не является общей проблемой, и коллеги часто не знают, какую функцию вы пишете. параметра, а рефакторинг кода тоже очень хлопотный.
Итак, исходя из этого требования, имеемTypescript
а такжеFlow
производится, ноTypeScript
Стоимость обучения относительно велика, и вообще говоря, вы не будете учить язык для какого-то удобства, поэтомуFacebook
Открытый исходный код четыре года назадFlow.
Vue
зачем использоватьFlow
вместоTypescript
Что насчет фреймворков разработки?Ответ Юйси Чжиху таков:.
Как им пользоваться, вы можете узнать, просмотрев китайскую документацию здесь. В этой статье в основном рассказывается о навыках работы с исходным кодом Vue и не будет объясняться слишком много Flow.
Архитектура проекта
Vue.js — типичный фреймворк MVVM, основная идея которого — управление данными и компонентизация. DOM — это естественное отображение данных.В Vue вам нужно только изменить данные, чтобы достичь цели обновления DOM. Компонентизация заключается в том, чтобы рассматривать каждый независимый функциональный блок или интерактивный модуль на странице как компонент, а страницу рассматривать как контейнер, чтобы реализовать搭积木式
метод развития.
Скачиваем исходники на локалку и давайте посмотрим на структуру каталогов
Структура каталогов
Разделение исходного каталога VUE четкое. Весь каталог примерно разделен на
-
benchmarks
: Тестовая демонстрация при обработке больших объемов данных -
dist
: Пакеты версий, необходимые для каждой среды. -
examples
: Некоторые практические демонстрации, реализованные с помощью Vue. -
flow
: Конфигурация определения типа данных -
packages
: подключаемый модуль, который необходимо установить отдельно для работы в определенной среде. -
src
: Ядро всего исходного кода. -
script
: файл конфигурации скрипта npm -
test
: прецедент -
types
: Новая версия конфигурации машинописного текста.
Основной код находится вsrc
Каталог, который включает создание экземпляров, обработку данных, компиляцию шаблонов, центр событий, глобальную конфигурацию и т. д., находится в этом каталоге.
от входа
В компиляторе найдите корневой каталогpackage.json
файл, который можно увидеть вscript
есть одинdev
, этот файл генерируетrollup
конфигурация упаковщика,
rollup -w -c scripts/config.js --environment TARGET:web-full-dev
rollup
означает, что он используетrollup
упаковщик,-w
выражатьwatch
отслеживать изменения файлов,c
выражатьconfig
Используйте файл конфигурации для упаковки, если файл не указан позже, он будет указан по умолчаниюrollup.config.js
, за которым следует указанныйscripts/config.js
настроить сборку,--environment
Указывает на установку переменных среды, за которыми следуют параметрыTARGET:web-full-dev
Указывает имя и значение переменной среды, и мы переходим кscripts/config.js
, Вы можете видеть, что параметры переменных среды были объединены и активированы.genConfig()
функция
genConfig()
Что вы наделали
Других скрытых предметов я пока не вижу, во-первыхconst opts = builds[name]
существуетbuilds
Поиск переменных в конфигурации. Файл ввода и конфигурация вывода определены, и если среда выполнения определена, она сохраняется в этом поле.Затем в этом файле найдитеweb-full-dev
Соответствующая конфигурация выглядит следующим образом: она в основном объявляет записьentry
и определение модуляformat
, выходdest
, имя средыenv
, сборка сводкиalias
, информация о кадреbanner
, вход естьweb/entry-runtime-with-compiler.js
,
но не в текущем каталогеweb
Папка, как найти? Выше мы видим, что естьresolve()
Функция прокси пути
использоватьsplit
Сократите входящие имена файлов, чтобы они соответствовали входящимalias
конфигурация, окончательное позиционированиеsrc/platforms/web/entry-runtime-with-compiler.js
, найдите, что Vue хранится здесь$mount
метод и заново объявляет$mount
метод, используя сохраненныйmount
Метод снова монтируется внизу и возвращает результат. Зачем нужно заново декларировать?После ознакомления с информацией знаю оригиналruntime-only
Версии не объявляются после объявления$mount
Таким образом, эту часть обработки можно повторно использовать на основе сохранения исходной функции, которую стоит изучить.
Нелегко изменить исходную логику, но исходную функцию можно сохранить и повторно объявить.
Общий процесс
Сначала посмотрите на общий процесс
- Первый рендер, выполнение
compileToFunctions()
Разберите шаблон шаблона в renderFn (функция рендеринга), пропустите эту часть, если renderFn уже существует - Передать renderFn через
vm._render()
Скомпилировал в Vnode, пока читал в нем переменные,Watcher
пройти черезObject.defindProperty()
изget
Метод собирает зависимости от dep и начинает слушать - воплощать в жизнь
updataComponent()
, сначала в домpatch()
метод будетvnode
Рендеринг в настоящий DOM - Смонтируйте DOM на узле и дождитесь изменения данных
-
data
При изменении атрибута сначала проверьте, есть ли ссылка на значение данных в собранных зависимостях.Object.defindProperty()
изset
Метод изменяет значение и выполняется_updata
провестиpatch()
а такжеupdataComponent()
Выполнить обновление компонента
грубо разделить на
Полная конструкция esm: включая компилятор шаблонов, HTML-строку процесса рендеринга → функцию рендеринга → VNode → настоящий DOM-узел
сборка среды выполнения только во время выполнения: не включает компилятор шаблонов, функцию рендеринга процесса рендеринга → VNode → реальный узел DOM
Версия только для времени выполнения не имеет шага template=>render и компилятора шаблонов.Объясните различную лексику
-
template
Шаблон: шаблон Vue основан на чистом HTML, основанном на синтаксисе шаблона Vue, и структура все еще может быть написана в предыдущем стиле HTML. - Абстрактное синтаксическое дерево AST:
Abstract Syntax Tree
Аббревиатура, в основном делать три шага- разбор: Vue использует HTML
Parser
Разобрать HTML-шаблон в AST - оптимизатор: выполнить некоторую оптимизацию AST, чтобы пометить статические узлы, извлечь самое большое статическое дерево, когда
_update
При обновлении интерфейса будет происходить процесс исправления, а алгоритм diff будет напрямую пропускать статические узлы, тем самым сокращая процесс исправления и оптимизируя производительность исправления. - generateCode: Генерировать из AST
render
функция
- разбор: Vue использует HTML
- Функция рендеринга renderFn: функция рендеринга используется для генерации
Virtual DOM(vdom)
из. Vue рекомендует использовать шаблоны для создания интерфейса нашего приложения.В базовой реализации Vue будет компилировать шаблоны вrenderFn函数
, конечно, мы также можем написать функцию рендеринга напрямую, без написания шаблона, чтобы лучше контролировать - Виртуальный DOM (vdom, также известный как VNode): дерево виртуального DOM, Vue's
Virtual DOM Patching
алгоритм основан наSnabbdom库
реализации, и внес множество корректировок и улучшений в эти основы. Может быть выполнено только через RenderFnvm._render()
генерировать,patch
целиVnode
, и каждыйVnode
глобально уникальный - патч: я упоминал об этом в вдоме выше, но я все же должен сказать, что патч является самым основным методом во всем виртаул-доме, основная функция заключается в исправлении
旧vnode
с новым vnodediff
процесс и, наконец, сгенерируйте новый узел DOM черезupdataComponent()
Метод повторно визуализируется, и Vue провел большую оптимизацию производительности для этого. - Наблюдатель: у каждого компонента Vue есть соответствующий
Watcher
,этоWatcher
будет в компонентеrender
Собирайте данные, от которых зависит компонент, когда компонент зависит, и запускайте компонент при обновлении зависимости.vm._updata
передачаpatch()
Сделайте diff и повторно визуализируйте DOM.
Не говори глупостей, пошли
устанавливать
Этот метод нового монтирования $mount.Vue.prototype.$mount = function (
el?: string | Element,
hydrating?: boolean
): Component {
el = el && query(el)
.....
key?:value
(key: value|void);
el?:string|Element
это синтаксис потока, указывающий, что входящая строка el может бытьstring
а такжеElement
И тип пустоты --undefined
Типы,hydrating?: boolean
Опять же, должен иметь тип boolean иundefined
.
key:?value
(key: value|void|null);
Значит этоkey
должно бытьvalue
илиundefined
так же какnull
Типы.
function():value
(:value):Component
Указывает, что возвращаемое значение функции должно бытьComponent
Типы.
function(key:value1|value2)
(ключ:значение1|значение2) выражатьkey
должно бытьvalue1
илиvalue2
Типы.
Скомпилировать RenderFn
el = el && query(el)
к входящемуel
Узел элемента подтвержден, если контейнер входящего узла не найден, он предупредит и вернетcreateElement('div')
новый разд.
//判断传入的标签如果是body或者是页面根节点
//就警告禁止挂载在页面根节点上,因为挂载会替换该节点。最后返回该节点
if (el === document.body || el === document.documentElement) {
process.env.NODE_ENV !== 'production' && warn(
`Do not mount Vue to <html> or <body> - mount to normal elements instead.`
)
return this
}
const options = this.$options;
if (!options.render) { //如果接受的值已经有写好的RenderFn,则不用进行任何操作,如果render不存在,就进入此逻辑将模板编译成renderFn
let template = options.template
if (template) {
... //有template就使用idToTemplate()解析,最终返回该节点的innerHTML
} if (typeof template === 'string') {
if (template.charAt(0) === '#') {//如果模板取到的第一个字符是#
template = idToTemplate(template)
if (process.env.NODE_ENV !== 'production' && !template) {//开发环境并且解析模板失败的报错:警告模板为空或者未找到
warn(
`Template element not found or is empty: ${options.template}`,
this
)
}
}
}else if (template.nodeType) {
//如果有节点类型,判定是普通节点,也返回innerHTML
template = template.innerHTML
} else {
//没有template就警告该模板无效
if (process.env.NODE_ENV !== 'production') {
warn('invalid template option:' + template, this)
}
return this
}
} else if (el) {
//如果是节点的话,获取html模板片段,getOuterHTML()对传入的el元素做了兼容处理,最终目的是拿到节点的outerHTML
//getOuterHTML()可以传入DOM节点,CSS选择器,HTML片段
template = getOuterHTML(el)
}
if (template) {
//编译HTML生成renderFn,赋给options,vm.$options.render此时发生变化
if (process.env.NODE_ENV !== 'production' && config.performance && mark) {
//开始标记
mark('compile')
}
/* compileToFunctions()主要是将getOuterHTML获取的模板编译成RenderFn函数,该函数的具体请往后翻看
* 具体步骤之后再说,编译大致主要分成三步
* 1.parse:将 html 模板解析成抽象语法树(AST)。
* 2.optimizer:对 AST 做优化处理。
* 3.generateCode:根据 AST 生成 render 函数。
*/
const { render, staticRenderFns } = compileToFunctions(template, {
shouldDecodeNewlines,
shouldDecodeNewlinesForHref,
delimiters: options.delimiters,
comments: options.comments
}, this)
options.render = render; //最后将解析的renderFn 赋值给当前实例
options.staticRenderFns = staticRenderFns //编译的配置
if (process.env.NODE_ENV !== 'production' && config.performance && mark) {
//结束标记
mark('compile end')
//根据mark()编译过程计算耗时差,用于到控制台performance查看阶段渲染性能
measure(`vue ${this._name} compile`, 'compile', 'compile end')
}
}
}
//最后返回之前储存的mount()方法进行挂载,如果此前renderFn存在就直接进行此步骤
return mount.call(this, el, hydrating)
}
Самое главное здесьcompileToFunctions()
Скомпилируйте шаблон в RenderFn, просмотрите этот метод через переход по каталогу.
template
Совместимость с различными методами письма и, наконец, получитьrenderFn
, и выполнять вспомогательные функции, такие как скрытые точки производительности в процессе.
наконецreturn mount.call(...)
это в
import Vue from './runtime/index'
Процесс компиляции более сложен, о чем будет рассказано позже. На данный момент обнаруживается, что метод прототипа Vue здесь не установлен, нам нужно перейти на верхний уровень.src/platforms/runtime/index.js
,
// 配置了一些全局的方法
Vue.config.mustUseProp = mustUseProp
Vue.config.isReservedTag = isReservedTag
Vue.config.isReservedAttr = isReservedAttr
Vue.config.getTagNamespace = getTagNamespace
Vue.config.isUnknownElement = isUnknownElement
// 安装平台的指令和组件
extend(Vue.options.directives, platformDirectives)
extend(Vue.options.components, platformComponents)
// 如果在浏览器里,证明不是服务端渲染,添加__patch__方法
Vue.prototype.__patch__ = inBrowser ? patch : noop
// 挂载$mount方法。
Vue.prototype.$mount = function (
el?: string | Element,
hydrating?: boolean
): Component {
//必须在浏览器环境才返回该节点,runtime-only版本会直接运行到这
el = el && inBrowser ? query(el) : undefined
return mountComponent(this, el, hydrating)
}
hydrating
Этот параметр можно понимать глобально как рендеринг на стороне сервера, и по умолчанию он равен false.
прошлойmountComponent(this, el, hydrating)
Фактически, этоupdate
а такжеwatcher
процесс. ПодробнееmountComponent
Что вы наделали. оказатьсяsrc/core/instance/lifecycle.js
, этот файл отвечает за добавление функций класса жизненного цикла для экземпляра.
export function mountComponent (
vm: Component,
el: ?Element,
hydrating?: boolean
): Component {
vm.$el = el //首先将vm.$el将传入的el做缓存,$el现在为真实的node
if (!vm.$options.render) {
//因为最后只认renderFn,如果没有的话,就创建一个空节点Vnode
vm.$options.render = createEmptyVNode
if (process.env.NODE_ENV !== 'production') {//开发环境下
if ((vm.$options.template && vm.$options.template.charAt(0) !== '#') ||
vm.$options.el || el) {
/*
(如果定义了template但是template首位不是'#')或者(没有传入element),就会警告当前使用的是runtime-only版本,
默认不带编译功能,如果需要编译的话,则需要更换构建版本,下面类似
*/
} else {
warn(//挂载组件失败:template或者renderFn未定义
'Failed to mount component: template or render function not defined.',
vm
)
}
}
}
// 在挂载之前为当前实例初始化beforMount生命周期
callHook(vm, 'beforeMount');
// 声明了一个 updateComponent 方法,这个是将要被 Watcher实例调用的更新组件的方法。
// 根据性能的对比配置不同的更新方法,
// performance+mark可以用于分析Vue组件在不同阶段中花费的时间,进而知道哪里可以优化。
let updateComponent
if (process.env.NODE_ENV !== 'production' && config.performance && mark) {
updateComponent = () => {
//获取组件标记
const name = vm._name
const id = vm._uid
const startTag = `vue-perf-start:${id}`
const endTag = `vue-perf-end:${id}`
mark(startTag);//标记开始节点
const vnode = vm._render();//生成一个Vnode
mark(endTag);//标记结束节点
//做performance命名'vue ${name} render',这样就可以在proformance中查看应用程序的运行状况、渲染性能,最后删除标记和度量
measure(`vue ${name} render`, startTag, endTag);
mark(startTag);
vm._update(vnode, hydrating);
mark(endTag)
measure(`vue ${name} patch`, startTag, endTag);
}
} else {
updateComponent = () => {
// 定义一个渲染watcher函数
// vm._render()里会调用render函数,并返回一个VNode,在生成VNode的过程中,会动态计算getter,同时推入到dep里面进行数据监听,每次数据更新后都出触发当前实例的_updata进行组件更新
// _update()方法会将新vnode和旧vnode进行diff比较,最后完成dom的更新工作,该方法请往下移步
vm._update(vm._render(), hydrating)
}
}
/* 新建一个_watcher对象,将监听目标推入dep,vm实例上挂载的_watcher主要是为了更新DOM调用当前vm的_watcher 的 update 方法。用来强制更新。为什么叫强制更新呢?
* vue里面有判断,如果newValue == oldValue, 那么就不触发watcher更新视图了
* vm:当前实例
* updateComponent:用来将vnode更新到之前的dom上
* noop:无效函数,可以理解为空函数
* {before(){...}}:配置,如果该实例已经挂载了,就配置beforeUpdate生命周期钩子函数
* true:主要是用来判断是哪个watcher的。因为computed计算属性和如果你要在options里面配置watch了同样也是使用了 new Watcher ,加上这个用以区别这三者
*/
new Watcher(vm, updateComponent, noop, {
before () {
if (vm._isMounted) {
callHook(vm, 'beforeUpdate')
}
}
}, true )
hydrating = false //关闭服务端渲染,服务端渲染只有created()和beforeCreate()
if (vm.$vnode == null) {
vm._isMounted = true
callHook(vm, 'mounted')
}
return vm
}
Конкретной функцией этой функции является монтирование узла и выполнение оперативной обработки данных.
Что касается того, почему есть заявление о суждении, которое нужно объявить в соответствии с условиемupdateComponent
метод, на самом деле по производительности видно, что один из методов используется для проверкиrender
а такжеupdate
представление. удобно вChrome=>performance
Просмотр производительности рендеринга в
process.env.NODE_ENV !== 'production' && config.performance && mark
Сначала определите текущую среду и поддерживает ли конфигурацияperformance
, а затем позвонитеmark
а такжеmeasure
метод, гдеmark
Инкапсулирует метод, на который может ссылаться конкретный API.MDN performance, Сделайте отметку на текущем элементе, а затем вернитесь к определенному моменту времени, основная функция - производительность скрытой точки
if (process.env.NODE_ENV !== 'production') {
//判断当前浏览器runtime是否支持performace
const perf = inBrowser && window.performance
if (
perf &&
perf.mark &&
perf.measure &&
perf.clearMarks &&
perf.clearMeasures
) {
mark = tag => perf.mark(tag);//标记该节点
measure = (name, startTag, endTag) => {
perf.measure(name, startTag, endTag)
//作性能埋点后,删除所有的标记和度量
perf.clearMarks(startTag)
perf.clearMarks(endTag)
perf.clearMeasures(name)
}
}
}
Как раз сейчасvm._update()
наверхуlifecyle.js
уже определено
Vue.prototype._update = function (vnode: VNode, hydrating?: boolean) {
//首先接收vnode
const vm: Component = this
const prevEl = vm.$el;//真实的dom节点
const prevVnode = vm._vnode;//之前旧的vnode
const prevActiveInstance = activeInstance;// null
activeInstance = vm;//获取当前的实例
vm._vnode = vnode;//当前新的vnode
if (!prevVnode) {
// 如果需要diff的旧vnode不存在,就无法进行__patch__
// 因此需要用新的vnode创建一个真实的dom节点
vm.$el = vm.__patch__(
vm.$el, //真实的dom节点
vnode, //传入的vnode
hydrating, //是否服务端渲染
false /* removeOnly是一个只用于 <transition-group> 的特殊标签,确保移除元素过程中保持一个正确的相对位置。 */)
} else {
// 如果需要diff的prevVnode存在,那么首先对prevVnode和vnode进行diff
// 并将需要的更新的dom操作已patch的形式打到prevVnode上,并完成真实dom的更新工作
vm.$el = vm.__patch__(prevVnode, vnode)
}
activeInstance = prevActiveInstance;//
// 如果存在真实的dom节点
if (prevEl) {
//就将之前的__vue__清空,再挂载新的
prevEl.__vue__ = null
}
// 将更新后的vm挂载到的vm__vue__上缓存
if (vm.$el) {
vm.$el.__vue__ = vm
}
// 如果当前实例的$vnode与父组件的_vnode相同,也要更新其$el
if (vm.$vnode && vm.$parent && vm.$vnode === vm.$parent._vnode) {
vm.$parent.$el = vm.$el
}
}
Как пропатчить?
__patch__
это всеvirtaul-dom
Среди них самый основной метод, основная функция которого заключается вprevVnode(旧vnode)
а также新vnode
провестиdiff
процесс, черезpatch
Сравните и, наконец, создайте новый узел реального дома, чтобы обновить вид измененной части.
существует/packages/factory.js
, определяетpatch()
, кода слишком много, извлекаются только важные части, в настоящее время процесс ясен, vue2.0+ является эталономsnabbdomУстановленный алгоритм исправления виртуального дома
return function patch (oldVnode, vnode, hydrating, removeOnly, parentElm, refElm) {
//用到的参数,oldVnode:旧的vnode、vnode:新的vnode、hydrating:服务端渲染、removeOnly:避免误操作
//当新的vnode不存在,并且旧的vnode存在时,直接返回旧的vnode,不做patch
if (isUndef(vnode)) {
if (isDef(oldVnode)) { invokeDestroyHook(oldVnode); }
return
}
var insertedVnodeQueue = [];
//如果旧的vnode不存在
if (isUndef(oldVnode)) {
//就创建一个新的节点
createElm(vnode, insertedVnodeQueue, parentElm, refElm);
} else {
//获取旧vnode的节点类型
var isRealElement = isDef(oldVnode.nodeType);
// 如果不是真实的dom节点并且属性相同
if (!isRealElement && sameVnode(oldVnode, vnode)) {
// 对oldVnode和vnode进行diff,并对oldVnode打patch
patchVnode(oldVnode, vnode, insertedVnodeQueue, removeOnly);
}
}
}
//最后返回新vnode的节点内容
return vnode.elm
}
Это базовый патч, предназначенный для/src/core/vdom/patch.js
изpatchVnode()
,
и черезsameVnode()
можно заранее сравнить旧vnode
а также新vnode
Основные свойства двух, этот метод определяет, будет ли следующийoldVnode
а такжеvnode
провестиdiff
function sameVnode (a, b) {
return (
a.key === b.key &&
a.tag === b.tag &&
a.isComment === b.isComment &&
isDef(a.data) === isDef(b.data) &&
sameInputType(a, b)
)
}
Это рассматривается только в том случае, если базовые свойства одинаковы.2个vnode
Происходит только локальное обновление, а потом это2个vnode
разница, если2个vnode
Есть несоответствие в основных свойствах , тогда оно будет пропущено напрямуюdiff
процесса, а затем на основеvnode
создать новый真实
dom, удаляя при этом старые узлы.
При рендеринге в первый разoldVnode
не существует, поэтому действуйте напрямуюdomcreateElm(vnode, insertedVnodeQueue, parentElm, refElm);
Создает новый узел, вместо этого существуетoldVnode
,когдаoldVnode
а такжеvnode
существуют иsameVnode(oldVnode, vnode)
Основные атрибуты двух узлов одинаковы, поэтому вводится процесс сравнения двух узлов.
существует/src/core/vdom/patch.js
Определите функцию patchVnode в
function patchVnode (oldVnode, vnode, insertedVnodeQueue, removeOnly) {
/*
* 比较新旧vnode节点,根据不同的状态对dom做合理的更新操作(添加,移动,删除)整个过程还会依次调用prepatch,update,postpatch等钩子函数,在编译阶段生成的一些静态子树
* 在这个过程中由于不会改变而直接跳过比对,动态子树在比较过程中比较核心的部分就是当新旧vnode同时存在children,通过updateChildren方法对子节点做更新,
* @param oldVnode 旧vnode
* @param vnode 新vnode
* @param insertedVnodeQueue 空数组,用于生命周期 inserted 阶段,记录下所有新插入的节点以备调用
* @param removeOnly 是一个只用于 <transition-group> 的特殊标签,确保移除元素过程中保持一个正确的相对位置。
*/
if (oldVnode === vnode) {
return
}
const elm = vnode.elm = oldVnode.elm
// 异步占位
if (isTrue(oldVnode.isAsyncPlaceholder)) {
if (isDef(vnode.asyncFactory.resolved)) {
hydrate(oldVnode.elm, vnode, insertedVnodeQueue)
} else {
vnode.isAsyncPlaceholder = true
}
return
}
//如果新vnode和旧vnode都是静态节点,key相同,或者新vnode是一次性渲染或者克隆节点,那么直接替换该组件实例并返回
if (isTrue(vnode.isStatic) &&
isTrue(oldVnode.isStatic) &&
vnode.key === oldVnode.key &&
(isTrue(vnode.isCloned) || isTrue(vnode.isOnce))
) {
vnode.componentInstance = oldVnode.componentInstance
return
}
// 可以往下翻去看vnode的例子,data是节点属性,包含class style attr和指令等
let i
const data = vnode.data
// 如果组件实例存在属性并且存在prepatch钩子函数就更新attrs/style/class/events/directives/refs等属性
if (isDef(data) && isDef(i = data.hook) && isDef(i = i.prepatch)) {
i(oldVnode, vnode)
}
const oldCh = oldVnode.children
const ch = vnode.children
//如果新的vnode带有节点属性,isPatchable返回是否含有组件实例的tag标签,两者满足
if (isDef(data) && isPatchable(vnode)) {
// cbs保存了hooks钩子函数: 'create', 'activate', 'update', 'remove', 'destroy'
for (i = 0; i < cbs.update.length; ++i) cbs.update[i](oldVnode, vnode)
// 取出cbs保存的update钩子函数,依次调用,更新attrs/style/class/events/directives/refs等属性
if (isDef(i = data.hook) && isDef(i = i.update)) i(oldVnode, vnode)
}
//如果vnode没有文本节点
if (isUndef(vnode.text)) {
//如果旧vnode和新vnode的子节点都存在
if (isDef(oldCh) && isDef(ch)) {
// 如果子节点不同,updateChildren就对子节点进行diff
if (oldCh !== ch) updateChildren(elm, oldCh, ch, insertedVnodeQueue, removeOnly)
//如果只存在新vnode
} else if (isDef(ch)) {
// 先将旧节点的文本清空
if (isDef(oldVnode.text)) nodeOps.setTextContent(elm, '')
// 然后将vnode的children放进去
addVnodes(elm, null, ch, 0, ch.length - 1, insertedVnodeQueue)
// 如果只存在旧vnode
} else if (isDef(oldCh)) {
// 就删除elm下的oldchildren
removeVnodes(elm, oldCh, 0, oldCh.length - 1)
// 如果只有旧vnode的文本内容
} else if (isDef(oldVnode.text)) {
// 直接清空内容
nodeOps.setTextContent(elm, '')
}
// 如果是两者文本内容不同
} else if (oldVnode.text !== vnode.text) {
// 直接更新vnode的文本内容
nodeOps.setTextContent(elm, vnode.text)
}
// 更新完毕后,执行 data.hook.postpatch 钩子,表明 patch 完毕
if (isDef(data)) {
if (isDef(i = data.hook) && isDef(i = i.postpatch)) i(oldVnode, vnode)
}
}
Используйте ловушки для обновления свойств узла, сравнивая новые свойства старого узла VNODE, дочерние элементы, типы узлов и содержимое.
Логика
Некоторые комментарии были добавлены в исходный код для понимания и логики.
- Если два vnode равны, нет необходимости исправлять.
- Если это асинхронный заполнитель, выполните
hydrate
метод или определениеisAsyncPlaceholder
верно, то выходит. - Если оба vnode являются статическими, их не нужно обновлять, поэтому предыдущий
componentInstance
Экземпляр передается текущему vnode. патч выхода - воплощать в жизнь
prepatch
крюк. - Перебрать и вызвать обратный вызов обновления по очереди, выполнить
update
крюк. Обновите атрибуты, такие как attrs/style/class/events/directives/refs. - Если оба vnode имеют дочерние элементы, а vnode не имеет текстового содержимого, и два vnode не равны, выполните
updateChildren
метод. Это суть виртуального DOM. - Если у нового vnode есть дочерние элементы, а у старого нет, очистите текст и добавьте узел vnode.
- Если у старого vnode есть дети, а у нового нет, очистите текст и удалите vnode.
- Если ни один vnode не имеет дочерних элементов, старый vnode имеет текст, а новый vnode не имеет текста, очистите текстовое содержимое DOM.
- Если текст старого vnode и нового vnode отличаются, обновите текстовое содержимое элемента DOM.
- передача
postpatch
Хук говорит, что патч сделан.
updateChildren
он немного круглый
function updateChildren (parentElm, oldCh, newCh, insertedVnodeQueue, removeOnly) {
/*
* @ parentElm 父元素
* @ oldCh 旧子节点
* @ newCh 新子节点
* @ insertedVnodeQueue 记录下所有新插入的节点以备调用
* @ removeOnly 是仅由<transition-group>使用的特殊标志,在离开过渡期间,确保删除的元素保持正确的相对位置
*/
let oldStartIdx = 0 //oldStartIdx => 旧头索引
let newStartIdx = 0 //newStartIdx => 新头索引
let oldEndIdx = oldCh.length - 1 //oldEndIdx => 旧尾索引
let oldStartVnode = oldCh[0] // 旧首索引节点,第一个
let oldEndVnode = oldCh[oldEndIdx] // 旧尾索引节点,最后一个
let newEndIdx = newCh.length - 1 //newEndIdx => 新尾索引
let newStartVnode = newCh[0] // 新首索引节点,第一个
let newEndVnode = newCh[newEndIdx] // 新首索引节点,最后一个
// 可以理解为
// 1. 旧子节点数组的 startIndex, endIndex, startNode, endNode
// 2. 新子节点数组的 startIndex, endIndex, startNode, endNode
let oldKeyToIdx, idxInOld, vnodeToMove, refElm
//可以进行移动
const canMove = !removeOnly
if (process.env.NODE_ENV !== 'production') {
//首先会检测新子节点有没有重复的key
checkDuplicateKeys(newCh)
}
while (oldStartIdx <= oldEndIdx && newStartIdx <= newEndIdx) {
if (isUndef(oldStartVnode)) {
oldStartVnode = oldCh[++oldStartIdx] // Vnode has been moved left
} else if (isUndef(oldEndVnode)) {
oldEndVnode = oldCh[--oldEndIdx]
//如果旧首索引节点和新首索引节点相同
} else if (sameVnode(oldStartVnode, newStartVnode)) {
//对旧头索引节点和新头索引节点进行diff更新, 从而达到复用节点效果
patchVnode(oldStartVnode, newStartVnode, insertedVnodeQueue)
//旧头索引向后
oldStartVnode = oldCh[++oldStartIdx]
//新头索引向后
newStartVnode = newCh[++newStartIdx]
//如果旧尾索引节点和新尾索引节点相似,可以复用
} else if (sameVnode(oldEndVnode, newEndVnode)) {
//旧尾索引节点和新尾索引节点进行更新
patchVnode(oldEndVnode, newEndVnode, insertedVnodeQueue)
//旧尾索引向前
oldEndVnode = oldCh[--oldEndIdx]
//新尾索引向前
newEndVnode = newCh[--newEndIdx]
} else if (sameVnode(oldStartVnode, newEndVnode)) { // Vnode moved right
/* 有一种情况,如果
* 旧【5,1,2,3,4】
* 新【1,2,3,4,5】,那岂不是要全删除替换一遍 5->1,1->2...?
* 即便有key,也会出现[5,1,2,3,4]=>[1,5,2,3,4]=>[1,2,5,3,4]...这样太耗费性能了
* 其实我们只需要将5插入到最后一次操作即可
*/
// 对旧首索引和新尾索引进行patch
patchVnode(oldStartVnode, newEndVnode, insertedVnodeQueue)
// 旧vnode开始插入到真实DOM中,旧首向右移,新尾向左移
canMove && nodeOps.insertBefore(parentElm, oldStartVnode.elm, nodeOps.nextSibling(oldEndVnode.elm))
oldStartVnode = oldCh[++oldStartIdx]
newEndVnode = newCh[--newEndIdx]
} else if (sameVnode(oldEndVnode, newStartVnode)) { // Vnode moved left
// 同上中可能,旧尾索引和新首也存在相似可能
// 对旧首索引和新尾索引进行patch
patchVnode(oldEndVnode, newStartVnode, insertedVnodeQueue)
// 旧vnode开始插入到真实DOM中,新首向左移,旧尾向右移
canMove && nodeOps.insertBefore(parentElm, oldEndVnode.elm, oldStartVnode.elm)
oldEndVnode = oldCh[--oldEndIdx]
newStartVnode = newCh[++newStartIdx]
} else {
//如果上面的判断都不通过,我们就需要key-index表来达到最大程度复用了
//如果不存在旧节点的key-index表,则创建
if (isUndef(oldKeyToIdx)) oldKeyToIdx = createKeyToOldIdx(oldCh, oldStartIdx, oldEndIdx)
//找到新节点在旧节点组中对应节点的位置
idxInOld = isDef(newStartVnode.key)? oldKeyToIdx[newStartVnode.key] : findIdxInOld(newStartVnode, oldCh, oldStartIdx, oldEndIdx)
//如果新节点在旧节点中不存在,就创建一个新元素,我们将它插入到旧首索引节点前(createElm第4个参数)
if (isUndef(idxInOld)) { // New element
createElm(newStartVnode, insertedVnodeQueue, parentElm, oldStartVnode.elm, false, newCh, newStartIdx)
} else {
// 如果旧节点有这个新节点
vnodeToMove = oldCh[idxInOld]
// 将新节点和新首索引进行比对,如果类型相同就进行patch
if (sameVnode(vnodeToMove, newStartVnode)) {
patchVnode(vnodeToMove, newStartVnode, insertedVnodeQueue)
// 然后将旧节点组中对应节点设置为undefined,代表已经遍历过了,不在遍历,否则可能存在重复插入的问题
oldCh[idxInOld] = undefined
// 如果不存在group群体偏移,就将其插入到旧首节点前
canMove && nodeOps.insertBefore(parentElm, vnodeToMove.elm, oldStartVnode.elm)
} else {
// 类型不同就创建节点,并将其插入到旧首索引前(createElm第4个参数)
// same key but different element. treat as new element
createElm(newStartVnode, insertedVnodeQueue, parentElm, oldStartVnode.elm, false, newCh, newStartIdx)
}
}
//将新首往后移一位
newStartVnode = newCh[++newStartIdx]
}
}
//当旧首索引大于旧尾索引时,代表旧节点组已经遍历完,将剩余的新Vnode添加到最后一个新节点的位置后
if (oldStartIdx > oldEndIdx) {
refElm = isUndef(newCh[newEndIdx + 1]) ? null : newCh[newEndIdx + 1].elm
addVnodes(parentElm, refElm, newCh, newStartIdx, newEndIdx, insertedVnodeQueue)
} //如果新节点组先遍历完,那么代表旧节点组中剩余节点都不需要,所以直接删除
else if (newStartIdx > newEndIdx) {
removeVnodes(parentElm, oldCh, oldStartIdx, oldEndIdx)
}
}
Vnode
существует/src/core/vdom/vnode.js
Есть определенные свойства Vnode
export default class VNode {
constructor (
tag?: string,
data?: VNodeData,
children?: ?Array<VNode>,
text?: string,
elm?: Node,
context?: Component,
componentOptions?: VNodeComponentOptions,
asyncFactory?: Function
) {
this.tag = tag //标签属性
this.data = data //渲染成真实DOM后,节点上到class attr style 事件等...
this.children = children //子节点,也上vnode
this.text = text // 文本
this.elm = elm //对应着真实的dom节点
this.ns = undefined //当前节点的namespace(命名空间)
this.context = context //编译的作用域
this.fnContext = undefined // 函数化组件上下文
this.fnOptions = undefined // 函数化组件配置项
this.fnScopeId = undefined // 函数化组件ScopeId
this.key = data && data.key //只有绑定数据下存在,在diff的过程中可以提高性能
this.componentOptions = componentOptions // 通过vue组件生成的vnode对象,若是普通dom生成的vnode,则此值为空
this.componentInstance = undefined //当前组件实例
this.parent = undefined // vnode、组件的占位节点
this.raw = false //是否为原生HTML或只是普通文本
this.isStatic = false //静态节点标识 || keep-alive
this.isRootInsert = true // 是否作为根节点插入
this.isComment = false // 是否为注释节点
this.isCloned = false //是否为克隆节点
this.isOnce = false //是否为v-once节点
this.asyncFactory = asyncFactory // 异步工厂方法
this.asyncMeta = undefined //异步Meta
this.isAsyncPlaceholder = false //是否为异步占位
}
//容器实例向后兼容的别名
get child (): Component | void {
return this.componentInstance
}
}
Другие атрибуты не важны. Наиболее важными атрибутами являются тег, данные, дочерние элементы, ключ и текст. VNode может относиться к следующим категориям
- TextVNode Текстовый узел.
- ElementVNode Обычный узел элемента.
- ComponentVNode Узел компонента.
- EmptyVNode Узел комментария без содержимого.
- Узел клона CloneVNode, который может быть любым из вышеперечисленных типов узлов, с той лишь разницей, что атрибут isCloned имеет значение true. Сначала мы определяем vnode
{
tag: 'div'
data: {
id: 'app',
class: 'test'
},
children: [
{
tag: 'span',
data:{
},
text: 'this is test'
}
]
}
Каждый слой объекта является узлом. вне
{
tag:'标签1',
attrs:{
属性key1:属性value1,
属性key2:属性value2,
...
},
children:[
{
tag:'子标签1',
attrs:{
子属性key1:子属性value1,
子属性key2:子属性value2,
...
},
children:[
{
....
}
]
},
{
tag:'子标签2',
attrs:{
子属性key1:子属性value1,
子属性key2:子属性value2,
...
},
children:[
{
....
}
]
}
]
}
Вложенный рекурсивный способ получения окончательного рендеринга
<div id="app" class="test">
<span>this is test</span>
</div>
Все дерево VNode, созданное деревом компонентов Vue, уникально. Это означает, что написанные от руки функции рендеринга не могут быть разделены на компоненты.
render: function (createElement) {
var myVnode = createElement('p', 'hi')
return createElement('div', [
myVnode, myVnode
])
}
Официальный способ - использовать фабричную функцию для
render: function (createElement) {
return createElement('div',
Array.apply(null, { length: 20 }).map(function () {
return createElement('p', 'hi')
})
)
}
почему ты хочешь сделать это? Понимание автора заключается в том, что createElement создает дочерний объект Vnode.В это время Vnode уже уникален.Если вы будете использовать его повторно в компоненте, он не будет уникальным.
Смотри, есть способ. объясните кстати
Может быть, вы думаете, что это слишком хлопотно, чтобы писать так прямоArray(20).map()
Как легко. но
new Array(20).map(function(v,i){
console.log(v,i);//不会输出任何东西,
})
map последовательно проходит только элементы индекса со значениями (включая undefined) и, наконец, последовательно формирует массив, потому чтоnew Array(20)
Если значение в массиве не инициализировано, результат вывода на печать[ empty * 20]
. Таким образом, после карты ничего не будет напечатано, потому что она не инициализирована, пуста и игнорируется.
Однако второй способ,Array.apply(null, {length: 20})
, выход[undefined,undefined,undefined....*20]
, является значением, которое было инициализировано и содержит 20undefined
массив . Плюсmap()
, то есть каждый разArray.apply(null,[undefined,undefined,.....]
, немного более знакомоArray(undefined,undefined,...*20)
, создайте 20 vnodes, зациклив return to createElement
Почему так сложно писать? Array.from ES6 может это сделать, но автору следует подумать о совместимости или о том, что можно сделать с ES5. Вздох, отличные базовые навыки...
compileToFunctions (шаблон компилируется в рендер)
первый в/src/platforms/web/compiler/index.js
определенныйcompileToFunctions()
метод,
// 设置编译的选项,不设置则使用默认配置,配置项比较多
import { baseOptions } from './options'
import { createCompiler } from 'compiler/index'
// 通过模板导入配置生成AST和Render
const { compile, compileToFunctions } = createCompiler(baseOptions)
export { compile, compileToFunctions }
Первый взгляд на импортированную конфигурацию
export const baseOptions: CompilerOptions = {
expectHTML: true,
modules,
directives,
isPreTag,
isUnaryTag,
mustUseProp,
canBeLeftOpenTag,
isReservedTag,
getTagNamespace,
staticKeys: genStaticKeys(modules)
}
вы можете увидеть определениеcompile
а такжеcompileToFunctions
, первое — это синтаксическое дерево AST, второе — скомпилированный файл renderFn.
import { parse } from './parser/index' // 将 HTML template解析为AST
import { optimize } from './optimizer' // 对AST优化标记处理,提取最大的静态树
import { generate } from './codegen/index' // 根据 AST 生成 render 函数
import { createCompilerCreator } from './create-compiler' //允许创建使用替代编译器,在这只使用默认部件导出默认编译器
export const createCompiler = createCompilerCreator(function baseCompile (
template: string,
options: CompilerOptions
): CompiledResult {
// parseHTML 的过程,导入配置,将template去掉空格,解析成AST ,最后返回AST元素对象
const ast = parse(template.trim(), options)
console.log(ast)
// 默认开始优化标记处理,否则不进行优化
if (options.optimize !== false) {
optimize(ast, options)
}
// 拿到最终的code。里面包含renderFn和静态renderFn
const code = generate(ast, options)
console.log(code.render)
//抛出
return {
ast,
render: code.render,
staticRenderFns: code.staticRenderFns
}
})
createCompilerCreator()
принимает параметр функции,createCompiler
Используется для создания компилятора, возвращаемое значениеcompile
так же какcompileToFunctions
.
export function createCompilerCreator (baseCompile: Function): Function {
return function createCompiler (baseOptions: CompilerOptions) {
function compile (
template: string,//模板
options?: CompilerOptions // 编译配置
): CompiledResult {
// 将finalOptions的隐式原型__proto__指向baseOptions对象
const finalOptions = Object.create(baseOptions)
const errors = []
const tips = []
finalOptions.warn = (msg, tip) => {
(tip ? tips : errors).push(msg)
}
// 如果导入了配置就将配置进行合并
if (options) {
// 合并分支模块
if (options.modules) {
finalOptions.modules =
(baseOptions.modules || []).concat(options.modules)
}
// 合并自定义指令
if (options.directives) {
finalOptions.directives = extend(
Object.create(baseOptions.directives || null),
options.directives
)
}
// 合并其他配置
for (const key in options) {
if (key !== 'modules' && key !== 'directives') {
finalOptions[key] = options[key]
}
}
}
// 将传入的函数执行,传入模板和配置项,得到编译结果
const compiled = baseCompile(template, finalOptions)
if (process.env.NODE_ENV !== 'production') {
errors.push.apply(errors, detectErrors(compiled.ast))
}
compiled.errors = errors
compiled.tips = tips
return compiled
}
return {
compile,
compileToFunctions: createCompileToFunctionFn(compile)
}
}
}
Наконец вcompile()
После выполнения уровня будет выброшена функция компиляции
compile
компилятор, который будетtemplate
преобразовать в соответствующийAST
Дерево,renderFn
так же какstaticRenderFns
функция
compileToFunctions
, выполнивcreateCompileToFunctionFn(compile)
получать,createCompileToFunctionFn()
является кэшированным компилятором, аstaticRenderFns
так же какrenderFn
будет преобразован вFuntion对象
. в конечном итоге скомпилирует
Разные платформы имеют некоторые отличияoptions
,такcreateCompiler
Согласно платформе, один будет передан вbaseOptions
, будет сcompile
прошел сам по себеoptions
объединиться, чтобы получить финалfinalOptions
.
export function createCompileToFunctionFn (compile: Function): Function {
// 声明缓存器
const cache = Object.create(null)
return function compileToFunctions (
template: string,
options?: CompilerOptions,
vm?: Component
): CompiledFunctionResult {
// 合并配置
options = extend({}, options)
const warn = options.warn || baseWarn
delete options.warn
//开发环境下尝试检测CSP,类似于用户浏览器设置,需要放宽限制否则无法进行编译,一般情况下可以忽略
if (process.env.NODE_ENV !== 'production') {
// detect possible CSP restriction
try {
new Function('return 1')
} catch (e) {
if (e.toString().match(/unsafe-eval|CSP/)) {
warn(
'It seems you are using the standalone build of Vue.js in an ' +
'environment with Content Security Policy that prohibits unsafe-eval. ' +
'The template compiler cannot work in this environment. Consider ' +
'relaxing the policy to allow unsafe-eval or pre-compiling your ' +
'templates into render functions.'
)
}
}
}
//有缓存的时候优先读取缓存的结果,并且返回 ,
const key = options.delimiters
? String(options.delimiters) + template
: template
if (cache[key]) {
return cache[key]
}
// 没有缓存结果则直接编译
const compiled = compile(template, options)
// 检查编译错误/提示
if (process.env.NODE_ENV !== 'production') {
if (compiled.errors && compiled.errors.length) {
warn(
`Error compiling template:\n\n${template}\n\n` +
compiled.errors.map(e => `- ${e}`).join('\n') + '\n',
vm
)
}
if (compiled.tips && compiled.tips.length) {
compiled.tips.forEach(msg => tip(msg, vm))
}
}
// 将代码转换成功能
const res = {}
const fnGenErrors = []
// 将render转换成Funtion对象
res.render = createFunction(compiled.render, fnGenErrors)
// 将staticRenderFns全部转化成Funtion对象
res.staticRenderFns = compiled.staticRenderFns.map(code => {
return createFunction(code, fnGenErrors)
})
//检查函数生成错误。只在编译器本身存在错误时才会发生,作者主要用于codegen开发使用
if (process.env.NODE_ENV !== 'production') {
if ((!compiled.errors || !compiled.errors.length) && fnGenErrors.length) {
warn(
`Failed to generate render function:\n\n` +
fnGenErrors.map(({ err, code }) => `${err.toString()} in\n\n${code}\n`).join('\n'),
vm
)
}
}
//最后存放在缓存中,下一次用可以进行读取
return (cache[key] = res)
}
}
Здесь есть кое-что интересное,
const cache = Object.create(null)
почему не напрямуюconst cache = {}
Шерстяная ткань? мы чувствуем
const cache = {}
унаследуетObject.prototype
Все методы-прототипы выше. в то время как null не будет, другой используетObject.create(null)
Причина в том, что использованиеfor..in
В цикле он проходит свойства в цепочке прототипов объектов, используяObject.create(null)
Конечно, больше нет необходимости проверять свойства, мы также можем напрямую использовать Object.keys[]. Если вы не думаете, что вам нужен очень чистый и настраиваемый объект в качестве словаря данных или вы хотите сохранитьhasOwnProperty
некоторая потеря производительности.
HTML для RenderFn
Давайте сначала напишем код
<div id="app"></div>
<script>
var vm = new Vue({
el:'#app',
template:`
<div @click="changeName()">
<span>{{name}}</span>
<ul>
<li v-for="(item,index) in like" :key="index">{{item}}</li>
</ul>
</div>`,
data:{
name:'Seven',
like:['旅游','电影','滑雪']
},methods:{
changeName(){
this.name = 'Floyd'
}
}
})
</script>
Давайте сначала посмотрим на его синтаксическое дерево AST,
Может быть, у вас немного кружится голова, все в порядке, нам не нужно заботиться об этом, абстракция, если вы можете ее понять, это называется абстракцией? Давайте снова посмотрим на функцию рендеринга
with(this){return _c('div',{on:{"click":function($event){changeName()}}},[_c('span',[_v(_s(name))]),_v(" "),_c('ul',_l((like),function(item,index){return _c('li',{key:index},[_v(_s(item))])}))])}
Для того, чтобы всем было легче увидеть структуру, требуется много усилий, чтобы вручную отформатировать следующие
with(this) {
return _c('div',
{
on: {
"click": function ($event) {
changeName()
}
}
},
[
_c('span', [ _v(_s(name)) ]),
_v(" "),
_c('ul',
_l( (like), function (item, index) {
return _c('li',
{
key: index
},
[
_v( _s(item) )
]
)
})
)
]
)
}
Может быть, некоторые люди думают, что они не могут понять большего, но ничего страшного, эту логику можно понять.
_c(
'标签名',
{
on:{//绑定
属性1:值,
属性2:值,
...
}
},
[//子节点
_c(
'标签名',
{
on:{//绑定
子属性1:值,
子属性2:值,
...
}
},
[
//子标签...
]
}
]
)
Скомпилируйте renderFn в Vnode
Из-за используемого синтаксиса with(this) все переменные в функции зависят от переменной this,_c
эквивалентноthis._c
эквивалентноvm._c
, мы печатаемvm._c
существуетСуть языка JavaScriptКак упоминалось в книге, старайтесь не использовать внутри своей функции
with()
синтаксис, это может сделать ваше приложение невозможным для отладки. Но You Yuxi использовал это так, используя замыкание, чтобы инкапсулировать его в функцию, так что не нужно беспокоиться об утечке.
ƒ (a, b, c, d) { return createElement(vm, a, b, c, d, false); }
/src/core/instance/render.js定义该方法
// 将 createElement 函数绑定到这个实例上以便在其中获得renderFn上下文。
vm._c = (a, b, c, d) => createElement(vm, a, b, c, d, false)
направлениеcreateElement()
функция, которая, в свою очередь, указывает на_createElement()
, функция определена в/src/core/vdom/create-element.js
. Окончательный возврат — это Vnode. Определение функции можно просмотреть в каталоге этой статьи.
Другие функции, которые мы можем использовать в/rc/core/instance/render-helper/index.js
найти определение в
export function installRenderHelpers (target: any) {
target._o = markOnce // v-once静态组件
target._n = toNumber // 判断是否数字,先parse再isNAN
target._s = toString // 需解析的文本,之前在parser阶段已经有所修饰
target._l = renderList // v-for节点
target._t = renderSlot // slot节点
target._q = looseEqual // 检测两个变量是否相等
target._i = looseIndexOf // 检测数组中是否包含与目标变量相等的项
target._m = renderStatic // 渲染静态内容
target._f = resolveFilter // filters处理
target._k = checkKeyCodes // 从config配置中检查eventKeyCode是否存在
target._b = bindObjectProps // 合并v-bind指令到VNode中
target._v = createTextVNode // 创建文本节点
target._e = createEmptyVNode // 注释节点
target._u = resolveScopedSlots // 处理ScopedSlots
target._g = bindObjectListeners // 处理事件绑定
}
createElement
var SIMPLE_NORMALIZE = 1;
var ALWAYS_NORMALIZE = 2;
function createElement (
context,
tag,
data,
children,
normalizationType,
alwaysNormalize
) {
// 兼容不传data的情况
if (Array.isArray(data) || isPrimitive(data)) {
normalizationType = children;
children = data;
data = undefined;
}
// 如果alwaysNormalize是true
// 那么normalizationType应该设置为常量ALWAYS_NORMALIZE的值
if (isTrue(alwaysNormalize)) {
normalizationType = ALWAYS_NORMALIZE;
}
// 调用_createElement创建虚拟节点
return _createElement(context, tag, data, children, normalizationType)
}
function _createElement (
context,
tag,
data,
children,
normalizationType
) {
/*
* 如果存在data.__ob__,说明data是被Observer观察的数据
* 不能用作虚拟节点的data
* 需要抛出警告,并返回一个空节点
*
* 被监控的data不能被用作vnode渲染的数据的原因是:data在vnode渲染过程中可能会被改变,这样会触发监控,导致不符合预期的操作
*
*/
if (isDef(data) && isDef((data).__ob__)) {
"development" !== 'production' && warn(
"Avoid using observed data object as vnode data: " + (JSON.stringify(data)) + "\n" +
'Always create fresh vnode data objects in each render!',
context
);
return createEmptyVNode()
}
// 当组件的is属性被设置为一个false的值
if (isDef(data) && isDef(data.is)) {
tag = data.is;
}
// Vue将不会知道要把这个组件渲染成什么,所以渲染一个空节点
if (!tag) {
return createEmptyVNode()
}
// 如果key是原始值,就警告key不能是原始值,必须string或者是number类型的值
if ("development" !== 'production' &&
isDef(data) && isDef(data.key) && !isPrimitive(data.key)
) {
{
warn(
'Avoid using non-primitive value as key, ' +
'use string/number value instead.',
context
);
}
}
// 作用域插槽
// 如果子元素是数组并且第一个是renderFn,就将其转移到scopedSlots
if (Array.isArray(children) &&
typeof children[0] === 'function'
) {
data = data || {};
data.scopedSlots = { default: children[0] };
children.length = 0;
}
// 根据normalizationType的值,选择不同的处理方法
if (normalizationType === ALWAYS_NORMALIZE) {
children = normalizeChildren(children);
} else if (normalizationType === SIMPLE_NORMALIZE) {
children = simpleNormalizeChildren(children);
}
var vnode, ns;
//如果标签名是string类型
if (typeof tag === 'string') {
var Ctor;
// 取到如果当前有自己的vnode和命名空间 或者 获取标签名的命名空间
ns = (context.$vnode && context.$vnode.ns) || config.getTagNamespace(tag);
// 判断是否为保留标签
if (config.isReservedTag(tag)) {
// 如果是保留标签,就创建一个这样的vnode
vnode = new VNode(
config.parsePlatformTagName(tag), data, children,
undefined, undefined, context
);// 如果不是保留标签,那么我们将尝试从vm实例的components上查找是否有这个标签的定义,自定义组件
} else if (isDef(Ctor = resolveAsset(context.$options, 'components', tag))) {
// 如果找到了这个标签的定义,就以此创建虚拟组件节点
vnode = createComponent(Ctor, data, context, children, tag);
} else {
// 保底方案,正常创建一个vnode
// unknown or unlisted namespaced elements
// check at runtime because it may get assigned a namespace when its
// parent normalizes children
vnode = new VNode(
tag, data, children,
undefined, undefined, context
);
}
} else {
// direct component options / constructor
// 当tag不是字符串的时候,就是组件的构造类,直接创建
vnode = createComponent(tag, data, context, children);
}
// 如果vnode是数组,直接返回。
if (Array.isArray(vnode)) {
return vnode
//如果有vnode
} else if (isDef(vnode)) {
// 如果有namespace,就应用下namespace,然后返回vnode
if (isDef(ns)) { applyNS(vnode, ns); }
// 如果定义了数据,就将其深度遍历,针对于class或者是style
if (isDef(data)) { registerDeepBindings(data); }
return vnode
} else {
//保底创建空VNode
return createEmptyVNode()
}
}
Посмотрите блок-схему
new Vue
оказатьсяsrc/core/instance/index.js
this._init(options)
. Почему (этот экземпляр Vue) может определить, используется ли новый оператор?
Вызов конструктора с новым проходит через 4 шага:
- создать новый объект;
- назначьте область конструктора новому объекту (чтобы это указывало на новый объект);
- выполнить код в конструкторе (добавить свойства к этому новому объекту);
- Вернуть новый объект.
а также
instanceof
Используется для обнаружения конструкторов Vue.prototype
Существует ли вthis
в цепочке прототипов, другими словами, если вы используетеnew
При создании экземпляраthis
указывает на этот вновь созданный объект, затемthis instanceof Vue
Смысл этого предложения в том, чтобы оценить, относится ли вновь созданный объект к типу Vue, что эквивалентно оценке нового экземпляра объекта.constructor
Это конструктор Vue.
Продолжение следует...Постоянное обновление