init
if (options && options._isComponent) {
// 如果是组件,不需要合并选项
// 由于合并操作很慢,并且需要特殊处理
initInternalComponent(vm, options)
} else {
// 序列化 props,Inject,Directives
// 合并父类和mixins的选项
vm.$options = mergeOptions(
resolveConstructorOptions(vm.constructor),
options || {},
vm
)
}
//初始化生命周期相关的标记
initLifecycle(vm)
//初始化事件相关的标记
//如果设置了 listeners, 需要更新父级传下的事件
initEvents(vm)
//初始化渲染相关的标记,如 _vnode _staticTrees slots createElement
//代理 attrs listeners
initRender(vm)
callHook(vm, 'beforeCreate')
//代理 Inject
initInjections(vm) // resolve injections before data/props
//主要是初始化 props data computed methods, 检查key是否重复
initState(vm)
initProvide(vm) // resolve provide after data/props
callHook(vm, 'created')
if (vm.$options.el) {
vm.$mount(vm.$options.el)
}
template check
if (template) {
if (typeof template === 'string') {
// 如果是id
if (template.charAt(0) === '#') {
template = idToTemplate(template)
}
} else if (template.nodeType) {
// 如果是原始引用
template = template.innerHTML
}
} else if (el) {
// 如果没有 `template`
template = getOuterHTML(el)
}
compiler
compiler主要分成3步
const ast = parse(template.trim(), options)
if (options.optimize !== false) {
optimize(ast, options)
}
const code = generate(ast, options)
parser
分词
使用正则匹配 标签 属性 文本
wihle(html){
comment.test(html) //匹配注释
html.match(doctype)//匹配文档类型
html.match(endTag) //匹配结束标签
parseEndTag(endTagMatch[1], curIndex, index)
parseStartTag() //匹配开始标签
handleStartTag(startTagMatch) //处理标签
}
转换
AST 元素节点总共有 3 种类型,
- 为 1 表示是普通元素
- 为 2 表示是表达式
- 为 3 表示是纯文本,注释节点或者是文本节点
//原始的ast starthandler
let element = createASTElement(tag, attrs, currentParent)
preTransforms() //内置转换
if("v-pre"){
processRawAttrs() //原始attr
}else {
processFor(element)
processIf(element)
processOnce(element)
// 处理元素 比如 ref slot is attr
processElement(element, options) {
processKey(element)
element.plain = !element.key && !element.attrsList.length
processRef(element)
processSlot(element)
processComponent(element) //is
transformNode()
processAttrs(element)
}
}
transforms 外部扩展预留钩子
ast里有3个transforms钩子,用户外部扩展使用,本质上等同于processXXX
- preTransformNode
- transformNode
- postTransformNode( 这个在endhandler里 )
web平台的版本提供了3个模块
- class
- style
- v-model
optimize
主要是标记静态根 ,为后面的渲染做一些优化。
// first pass: mark all non-static nodes.
markStatic(root)
// second pass: mark static roots.
markStaticRoots(root, false)
静态节点是指没事表达式或者没有v-for、v-if或者有v-once等等
静态根除了本身是一个静态节点外,必须满足拥有 children,并且 children 不能只是一个文本节点
静态根有2种
-
普通的静态根
普通的静态根会生成一个独立的渲染函数,保存在 options.staticRenderFns 里。
需要在render函数里使用 renderStatic (
Vue.prototype._m)缓存静态根,实例缓存在 vm._staticTrees -
在 v-for 列表渲染内的静态根
这种静态根的渲染函数不会保存在 options.staticRenderFns 里,实例也不会缓存在 vm._staticTrees
在对比新旧节点的时候的时候,vnode.isonce 为 true 的节点,不会生成新的实例
可以在render函数里使用 markOnce (
Vue.prototype._o) 标记下节点。
if (isTrue(vnode.isStatic) &&
isTrue(oldVnode.isStatic) &&
vnode.key === oldVnode.key &&
(isTrue(vnode.isCloned) || isTrue(vnode.isOnce))
) {
vnode.componentInstance = oldVnode.componentInstance
return
}
generate code
generate code 就不添加主流程代码,基本就是把所有的东西都处理一遍,下面是v-if的生成方式。
function genIfConditions(){
const condition = conditions.shift()
if (condition.exp) {
return `(${condition.exp})?${
genTernaryExp(condition.block)
}:${
genIfConditions(conditions, state, altGen, altEmpty)
}`
} else {
return `${genTernaryExp(condition.block)}`
}
}
mount component
渲染组件主要有三个步骤
- 生成 vnode
- 更新 实例
- 收集 依赖
function mountComponent{
updateComponent = () => {
vm._update(vm._render(), hydrating)
}
new Watcher(vm, updateComponent, noop, {
before() {
if (vm._isMounted) {
callHook(vm, 'beforeUpdate')
}
}
}, true /* isRenderWatcher */)
}
create element
使用 createElement 可以创建 vnode,createElement会当初render的一个参数传入, 或者使用 Vue.prototype.$createElement 。
createElement 首先会先优化参数和规范化子树,再根据tag创建元素vnode或者组件vnode。
function createElement (){
if (normalizationType === ALWAYS_NORMALIZE) {
children = normalizeChildren(children)
} else if (normalizationType === SIMPLE_NORMALIZE) {
children = simpleNormalizeChildren(children)
}
if (typeof tag === 'string') {
if (config.isReservedTag(tag)) {
vnode = new VNode(
config.parsePlatformTagName(tag), data, children,
undefined, undefined, context
)
} else if (isDef(Ctor = resolveAsset(context.$options, 'components', tag))) {
vnode = createComponent(Ctor, data, context, children, tag)
} else {
vnode = new VNode(
tag, data, children,
undefined, undefined, context
)
}
}else{
vnode = createComponent(tag, data, context, children)
}
}
create component
createComponent 会创建一个组件vnode。组件vnode.children 为空, 但会有个componentInstance,本质上是个Vue实例。
组件默认内置了4类的生命周期函数供diff期间调用。
- init
- prepatch
- insert
- destroy
diff期间还有其他的生命周期函数
- create
- update
- postpatch
- remove
- activate
由于在template中,所以的未知属性都是被当成元素的属性,所以是无法注册额外的生命周期函数,但是render函数中是可以注册组件生命周期函数的。
render(h){
return h('Component',{
hook: {
init(){
console.log("init")
}
}
})
}
一般情况没有必要注册组件生命周期函数,非要在父级注册组件的生命周期函数,可以这样注册实例的生命周期函数。
render(h){
return h('Component',{
hook: {
on: {
"hook:created"() {
console.log("hook:created")
}
}
}
})
}
组件有4种特殊类型
- 异步组件 async component
- 函数组件 functional component
- 抽象组件 abstract component
- 递归组件 recyclable component
function createComponent(){
//异步组件 async component
if (isUndef(Ctor.cid)) {
asyncFactory = Ctor
Ctor = resolveAsyncComponent(asyncFactory, baseCtor, context)
if (Ctor === undefined) {
return createAsyncPlaceholder(
asyncFactory,
data,
context,
children,
tag
)
}
}
//函数组件 functional component
if (isTrue(Ctor.options.functional)) {
return createFunctionalComponent(Ctor, propsData, data, context, children)
}
//抽象组件 abstract component
if (isTrue(Ctor.options.abstract)) {
const slot = data.slot
data = {}
if (slot) {
data.slot = slot
}
}
// 注册组件生命周期函数
installComponentHooks(data)
//生成组件的vnode
const name = Ctor.options.name || tag
const vnode = new VNode(
`vue-component-${Ctor.cid}${name ? `-${name}` : ''}`,
data, undefined, undefined, undefined, context,
{ Ctor, propsData, listeners, tag, children },
asyncFactory
)
//递归组件 recyclable component
if (__WEEX__ && isRecyclableComponent(vnode)) {
return renderRecyclableComponentTemplate(vnode)
}
return vnode
}
create functional component
函数式组件更像一个闭包函数,有自己的上下文,返回的vnode和普通元素生成的vnode一样,不像组件的vnode那样子代都保存在componentInstance里,并且没有要求只有1个根节点,甚至不会随便编译template。
function createFunctionalComponent(){
const renderContext = new FunctionalRenderContext(
data,
props,
children,
contextVm,
Ctor
)
const vnode = options.render.call(null, renderContext._c, renderContext)
// 克隆,避免复用时使用同一个vnode
return cloneAndMarkFunctionalResult(vnode)
}
resolve async component
异步组件本质上就是在不同阶阶段返回不同的组件,在状态改变的时候调用Vue.prototype.$forceUpdate更新视图。
function resolveAsyncComponent(){
//根据不同的状态,返回不同的组件
if (isTrue(factory.error) && isDef(factory.errorComp)) {
return factory.errorComp
}
if (isDef(factory.resolved)) {
return factory.resolved
}
if (isTrue(factory.loading) && isDef(factory.loadingComp)) {
return factory.loadingComp
}
if (isDef(factory.contexts)) {
factory.contexts.push(context)
} else {
const contexts = factory.contexts = [context]
let sync = true
const forceRender = () => {
for (let i = 0, l = contexts.length; i < l; i++) {
contexts[i].$forceUpdate()
}
}
// 可以看出 resolve 和 reject 内部都会强制更新视图
const resolve = once((res) => {
factory.resolved = ensureCtor(res, baseCtor)
if (!sync) {
forceRender()
}
})
const reject = once(reason => {
if (isDef(factory.errorComp)) {
factory.error = true
forceRender()
}
})
const res = factory(resolve, reject)
}
built-in components
keep-alive
keep-alive 本质上就是看是否命中缓存,命中就是取出,没命中就新增
{
name: 'keep-alive',
render(){
const slot = this.$slots.default
const vnode = getFirstComponentChild(slot)
const componentOptions = vnode && vnode.componentOptions
// 可以看出只对组件有效,普通节点没有状态,没必要缓存
if (componentOptions){
const { cache, keys } = this
const key = vnode.key
if (cache[key]) {
vnode.componentInstance = cache[key].componentInstance
remove(keys, key)
keys.push(key)
} else {
cache[key] = vnode
keys.push(key)
// 缓存上限检测
if (this.max && keys.length > parseInt(this.max)) {
pruneCacheEntry(cache, keys[0], keys, this._vnode)
}
}
}
}
return vnode || (slot && slot[0])
}
transition
transition的组件本身只是用来接收一些参数,实现过度效果是依靠在diff期间执行create,activate,remove 这3个生命周期函数。
function patch (){
//...
if (isDef(vnode.parent)) {
let ancestor = vnode.parent
const patchable = isPatchable(vnode)
while (ancestor) {
ancestor.elm = vnode.elm
if (patchable) {
for (let i = 0; i < cbs.create.length; ++i) {
// 这里会回调 transition 钩子
cbs.create[i](emptyNode, ancestor)
}
} else {
registerRef(ancestor)
}
ancestor = ancestor.parent
}
}
//...
}
截一段添加css class的代码,给元素添加上class,就可以实现过度效果了。
function create(){
// 确保是个 Transition 组件
const data = resolveTransition(vnode.data.transition)
if (isUndef(data)) {
return
}
if (expectsCSS) {
addTransitionClass(el, startClass)
addTransitionClass(el, activeClass)
nextFrame(() => {
removeTransitionClass(el, startClass)
if (!cb.cancelled) {
addTransitionClass(el, toClass)
if (!userWantsControl) {
if (isValidDuration(explicitEnterDuration)) {
setTimeout(cb, explicitEnterDuration)
} else {
whenTransitionEnds(el, type, cb)
}
}
}
})
}
}
transition-group
首先区分当前节点中,哪些是原有,哪些的移除,顺便标记下位置和添加过度属性。
render(){
const prevChildren = this.prevChildren = this.children
const rawChildren = this.$slots.default || []
for (let i = 0; i < rawChildren.length; i++) {
const c = rawChildren[i]
if (c.tag) {
if (c.key != null && String(c.key).indexOf('__vlist') !== 0) {
children.push(c)
map[c.key] = c;
(c.data || (c.data = {})).transition = transitionData
}
}
}
if (prevChildren) {
const kept = []
const removed = []
for (let i = 0; i < prevChildren.length; i++) {
const c = prevChildren[i]
c.data.transition = transitionData
c.data.pos = c.elm.getBoundingClientRect()
if (map[c.key]) {
kept.push(c)
} else {
removed.push(c)
}
}
this.kept = h(tag, null, kept)
this.removed = removed
}
return h(tag, null, children)
}
再使用修改后的update更新视图,先移除节点,在添加和移动节点。其目的是为了保证diff的稳定性。
beforeMount() {
const update = this._update
this._update = (vnode, hydrating) => {
// 先移除节点
this.__patch__(
this._vnode,
this.kept,
false, // hydrating
true // removeOnly (!important, avoids unnecessary moves)
)
this._vnode = this.kept
// 再新增节点和移动节点
update.call(this, vnode, hydrating)
}
},
到这步,由于新增和移除的节点都带有过度属性,在diff算法中会执行transition的生命周期函数实现过度效果,但是移动节点还没有实现过度,并且位置已经被更新。
在 transtion-group 在 updated生命函数处理移动节点,实现了过度效果。
updated() {
if (!children.length || !this.hasMove(children[0].elm, moveClass)) {
return
}
//执行先前的回调
children.forEach(callPendingCbs)
//记录位置
children.forEach(recordPosition)
//由于节点已经处在更新后的位置
//所以对比前后位置,使用 translate 回归到之前的位置
children.forEach(applyTranslation)
//强制触发浏览器重绘
this._reflow = document.body.offsetHeight
children.forEach((c) => {
if (c.data.moved) {
addTransitionClass(el, moveClass)
// 执行回调
})
}
hasMove是用来检测是否有移动节点的动画,其原理就是
- 克隆一个子节点
- 添加上class
- 插入文档中
- 检测getComputedStyle中有没有过度或者动画属性
- 移除文档
function(){hasMove(el, moveClass) {
const clone = el.cloneNode()
addClass(clone, moveClass)
clone.style.display = 'none'
this.$el.appendChild(clone)
const info = getTransitionInfo(clone)
this.$el.removeChild(clone)
return info.hasTransform
}