// file:/src/platform/web/runtime/index.js
Vue.prototype.$mount = el => return mountComponent(vm, el)
// file:/src/core/instance/lifecycle.js
updateComponent = () => {
vm._update(vm._render())
}
编译外置
平时写vue代码使用了SPA:
vue-cli=>webpack=>vue-loader,项目中引入vue,node_modules中 vue 也指向"main": "dist/vue.runtime.common.js",只包含运行时版 。
// 平台入口处 `/src/platform/web/entry-runtime-with-compiler.js`复写了$mount方法
const mount = Vue.prototype.$mount
Vue.prototype.$mount = function(){
...
const { render, staticRenderFns } = compileToFunctions(template...)
...
return mount.call(this, el, hydrating)
}
渲染函数
vm._render()返回 VNode
initRender
// file:/src/core/instance/render.js
vm._c = (a, b, c, d) => createElement(vm, a, b, c, d, false)
createElement
// file:/src/core/vdom/create-element.js
// 包装参数,传递给 _createElement
createElement = (context: Component, tag: any, data: any, children: any, n:any
alwaysNormalize: boolean)
=> return _createElement(context, tag, data, children, n)
ns 在 svg 或 math 的命名空间内
_createElement
// 1. 拦截data、tag为空 return createEmptyVNode(),警告key为基本类型
// 2. 处理children为数组:若为单子级,则设为default插槽
// 3. 创建 *组件VNode* 或 *常规VNode*
// 4. 返回VNode
export function _createElement (
context: Component,
tag?: string | Class<Component> | Function | Object,
data?: VNodeData,
children?: any,
normalizationType?: number
): VNode | Array<VNode> {
// 1. 拦截data、tag为空 return createEmptyVNode(),警告key为基本类型
if (isDef(data) && isDef((data: any).__ob__)) {
return createEmptyVNode()
}
if (isDef(data) && isDef(data.is)) {
tag = data.is
}
if (!tag) {
return createEmptyVNode()
}
if (process.env.NODE_ENV !== 'production' &&
isDef(data) && isDef(data.key) && !isPrimitive(data.key)) { ...warning }
// 2. 处理children为数组:若为单子级,则设为default插槽
if (Array.isArray(children) && typeof children[0] === 'function') {
data = data || {}
data.scopedSlots = { default: children[0] }
children.length = 0
}
if (normalizationType === ALWAYS_NORMALIZE) {
children = normalizeChildren(children)
} else if (normalizationType === SIMPLE_NORMALIZE) {
children = simpleNormalizeChildren(children)
}
// 3. 创建 *组件VNode* 或 *常规VNode*
let vnode, ns
if (typeof tag === 'string') {
let Ctor
ns = (context.$vnode && context.$vnode.ns) || config.getTagNamespace(tag)
if (config.isReservedTag(tag)) {
vnode = new VNode(
config.parsePlatformTagName(tag), data, children,
undefined, undefined, context
)
} else if ((!data || !data.pre) && 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)
}
// 4. 返回VNode
if (Array.isArray(vnode)) {
return vnode
} else if (isDef(vnode)) {
if (isDef(ns)) applyNS(vnode, ns)
if (isDef(data)) registerDeepBindings(data)
return vnode
} else {
return createEmptyVNode()
}
}
- createComponent
// 1. 前处理
// 2. 装载数据:mixin 重注、v-model语法糖、propsData依赖、组件事件、注入回调
// 3. 返回
export function createComponent (
Ctor: Class<Component> | Function | Object | void,
data: ?VNodeData,
context: Component,
children: ?Array<VNode>,
tag?: string
): VNode | Array<VNode> | void {
// 1. 前处理
if (isUndef(Ctor)) return
const baseCtor = context.$options._base
if (isObject(Ctor)) {
Ctor = baseCtor.extend(Ctor)
}
if (typeof Ctor !== 'function') return
// 服务端渲染
let asyncFactory
if (isUndef(Ctor.cid)) {
asyncFactory = Ctor
Ctor = resolveAsyncComponent(asyncFactory, baseCtor)
if (Ctor === undefined) {
return createAsyncPlaceholder( ... )
}
}
// 2. 装载数据
data = data || {}
resolveConstructorOptions(Ctor) // mixin 重注入,以防丢失
if (isDef(data.model)) transformModel(Ctor.options, data) // v-model语法糖
const propsData = extractPropsFromVNodeData(data, Ctor, tag)
// a.提取属性prop、attr到propsData
// b. 若有响应值,则此时产生依赖,父级向子组件传递响应值
if (isTrue(Ctor.options.functional)) {
// 函数式组件
return createFunctionalComponent(Ctor, propsData, data, context, children)
}
const listeners = data.on // 组件自身调用事件
data.on = data.nativeOn // 原生事件流
installComponentHooks(data) // 注入回调init、prepatch、insert、destroy
// 3. 返回
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
)
return vnode
}
createTextVNode
_v() 指向=> createTextVNode()
// val 常常为 *响应值*,所以常从此处依赖
export function createTextVNode (val: string | number) {
return new VNode(undefined, undefined, undefined, String(val)) // this.text = text
}
虚拟dom
浏览器的标准把 DOM 设计的非常复杂。频繁的去更新 DOM ,会产生一定的性能问题。
- 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
this.children = children
this.text = text
this.elm = elm
this.ns = undefined
this.context = context
this.fnContext = undefined
this.fnOptions = undefined
this.fnScopeId = undefined
this.key = data && data.key
this.componentOptions = componentOptions
this.componentInstance = undefined
this.parent = undefined
this.raw = false
this.isStatic = false
this.isRootInsert = true
this.isComment = false
this.isCloned = false
this.isOnce = false
this.asyncFactory = asyncFactory
this.asyncMeta = undefined
this.isAsyncPlaceholder = false
}
}
Virtual DOM 是借鉴了一个开源库 snabbdom 引自Vue.js 技术揭秘
DOM操作
vm._update() => vm.__patch__() => createPatchFunction() => patch()
Vue.prototype._update
// file:/src/core/instance/lifecycle.js
// 1. 记录前后值
// 2. 根据前后记录,来排队初始化渲染还是更新渲染
// 3. $el 标记更新
export function lifecycleMixin(Vue: Class<Component>) {
Vue.prototype._update = function (vnode: VNode, hydrating?: boolean) {
// 1. 记录前后值
const vm: Component = this
const prevEl = vm.$el
const prevVnode = vm._vnode
const restoreActiveInstance = setActiveInstance(vm) // 运行标记(单例)
vm._vnode = vnode
// 2. 对比前后,判断渲染
if (!prevVnode) {
vm.$el = vm.__patch__(vm.$el, vnode, hydrating, false) // 初始化
} else {
vm.$el = vm.__patch__(prevVnode, vnode) // 更新
}
restoreActiveInstance() // 运行标记(回溯)
// 3. HTML标记vue实例
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
}
}
Vue.prototype.$forceUpdate = function () {}
Vue.prototype.$destroy = function () {}
}
createPatchFunction
// file:/src/platform/web/runtime/patch.js
import baseModules from 'core/vdom/modules/index'
import platformModules from 'web/runtime/modules/index'
// 传递 平台元素操作的工具函数 与 vnode各模块生命周期回调
export const patch: Function = createPatchFunction({ nodeOps, modules })
patch
export function createPatchFunction (backend) {
// ... 辅助函数
return function patch (oldVnode, vnode, hydrating, removeOnly) {
// 1. 参数判断,选择入口
if (isUndef(vnode)) {
// 无新vnode 销毁
if (isDef(oldVnode)) invokeDestroyHook(oldVnode)
return
}
let isInitialPatch = false
const insertedVnodeQueue = []
if (isUndef(oldVnode)) {
// 无旧vnode 创建并设置 *插入回调队列*
isInitialPatch = true
createElm(vnode, insertedVnodeQueue)
} else {
const isRealElement = isDef(oldVnode.nodeType)
if (!isRealElement && sameVnode(oldVnode, vnode)) {
// **diff函数**
patchVnode(oldVnode, vnode, insertedVnodeQueue, null, null, removeOnly)
} else {
if (isRealElement) {
// 根实例初始化入口
// 服务端渲染
if (oldVnode.nodeType === 1 && oldVnode.hasAttribute(SSR_ATTR)) {
oldVnode.removeAttribute(SSR_ATTR)
hydrating = true
}
if (isTrue(hydrating)) {}
oldVnode = emptyNodeAt(oldVnode) // $el转换为vnode
}
const oldElm = oldVnode.elm
const parentElm = nodeOps.parentNode(oldElm)
createElm(
vnode,
insertedVnodeQueue,
oldElm._leaveCb ? null : parentElm,
nodeOps.nextSibling(oldElm)
)
// 递归更新父级占位node
if (isDef(vnode.parent)) {...}
// 销毁旧vnode
if (isDef(parentElm)) {
// 根实例清除$el
removeVnodes([oldVnode], 0, 0)
} else if (isDef(oldVnode.tag)) {
// 组件调用销毁钩子
invokeDestroyHook(oldVnode)
}
}
}
invokeInsertHook(vnode, insertedVnodeQueue, isInitialPatch)
return vnode.elm
}
}
invokeDestroyHook
// 销毁钩子
function invokeDestroyHook (vnode) {
let i, j
const data = vnode.data
if (isDef(data)) {
if (isDef(i = data.hook) && isDef(i = i.destroy)) i(vnode) // 组件钩子
for (i = 0; i < cbs.destroy.length; ++i) cbs.destroy[i](vnode) // 模块钩子
}
// 递归子级
if (isDef(i = vnode.children)) {
for (j = 0; j < vnode.children.length; ++j) {
invokeDestroyHook(vnode.children[j])
}
}
}
createElm
// 创建元素
function createElm (
vnode,
insertedVnodeQueue,
parentElm,
refElm,
nested,
ownerArray,
index
) {
// 判断入口,创建相应node
if (isDef(vnode.elm) && isDef(ownerArray)) {
vnode = ownerArray[index] = cloneVNode(vnode)
}
vnode.isRootInsert = !nested // transition 标记
// 组件入口
if (createComponent(vnode, insertedVnodeQueue, parentElm, refElm)) {
return
}
const data = vnode.data
const children = vnode.children
const tag = vnode.tag
if (isDef(tag)) {
// 标签入口
vnode.elm = vnode.ns
? nodeOps.createElementNS(vnode.ns, tag)
: nodeOps.createElement(tag, vnode)
setScope(vnode)
createChildren(vnode, children, insertedVnodeQueue) // 递归子级
if (isDef(data)) {
invokeCreateHooks(vnode, insertedVnodeQueue) // 调用模块
}
insert(parentElm, vnode.elm, refElm) // 注入
} else if (isTrue(vnode.isComment)) {
// 注释标签入口
vnode.elm = nodeOps.createComment(vnode.text)
insert(parentElm, vnode.elm, refElm)
} else {
// 文本入口
vnode.elm = nodeOps.createTextNode(vnode.text)
insert(parentElm, vnode.elm, refElm)
}
}
insert
function insert (parent, elm, ref) {
if (isDef(parent)) {
if (isDef(ref)) {
if (nodeOps.parentNode(ref) === parent) {
nodeOps.insertBefore(parent, elm, ref) // 兄弟级别插入
}
} else {
nodeOps.appendChild(parent, elm) // 插入子级
}
}
}
createChildren
function createChildren (vnode, children, insertedVnodeQueue) {
if (Array.isArray(children)) {
// 递归
for (let 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)))
}
}
createComponent
function createComponent (vnode, insertedVnodeQueue, parentElm, refElm) {
let i = vnode.data
if (isDef(i)) {
// keepAlive 包裹
const isReactivated = isDef(vnode.componentInstance) && i.keepAlive
if (isDef(i = i.hook) && isDef(i = i.init)) {
// 调用data.hook.init 初始化组件
i(vnode, false /* hydrating */)
}
// 必然设置了vnode.componentInstance
if (isDef(vnode.componentInstance)) {
initComponent(vnode, insertedVnodeQueue)
insert(parentElm, vnode.elm, refElm)
if (isTrue(isReactivated)) {
reactivateComponent(vnode, insertedVnodeQueue, parentElm, refElm)
}
return true
}
}
}
nodeOps
create
export function createElement (tagName: string, vnode: VNode): Element {
const elm = document.createElement(tagName)
if (tagName !== 'select') {
return elm
}
// false or null will remove the attribute but undefined will not
if (vnode.data && vnode.data.attrs && vnode.data.attrs.multiple !== undefined) {
elm.setAttribute('multiple', 'multiple')
}
return elm
}
export function createTextNode (text: string): Text {
return document.createTextNode(text)
}
export function createComment (text: string): Comment {
return document.createComment(text)
}
operate
export function insertBefore (parentNode: Node, newNode: Node, referenceNode: Node) {
parentNode.insertBefore(newNode, referenceNode)
}
export function removeChild (node: Node, child: Node) {
node.removeChild(child)
}
export function appendChild (node: Node, child: Node) {
node.appendChild(child)
}
export function setTextContent (node: Node, text: string) {
node.textContent = text
}
export function setStyleScope (node: Element, scopeId: string) {
node.setAttribute(scopeId, '')
}
relation
export function parentNode (node: Node): ?Node {
return node.parentNode
}
export function nextSibling (node: Node): ?Node {
return node.nextSibling
}
export function tagName (node: Element): string {
return node.tagName
}