new Vue
实例
// 整体流程
new Vue()
this._init(options)
//调用 initMixin(Vue) 中绑定的 _init 方法
Vue.prototype._init = function(options) { const vm = this }
// 一系列初始化操作 initLifecycle, initEvents, initRender ... initState, ...
// 挂载
vm.$mount(vm.$options.el) // #app
// 关注 initState 方法
export function initState(vm) {
// options合并
vm.$options = mergeOptions( ... )
const opts = vm.$options
initProps(vm, opts.props)
initMethods(vm, opts.methods)
initData(vm) // 关注点
initComputed(vm, opts.computed)
initWatch(vm, opts.watch)
}
// initData 方法
function initData(vm) {
const data = vm.$options.data
// data 是否为 function, 注意 vm._data
data = vm._data = typeof data === 'function' ? getData(data, vm) : data || {}
// 判断属性是否与 props, methods中定义的属性冲突
const keys = Object.keys(data)
// ...
// 通过 Object.defineProperty 代理 this._data.xxx 至 this.xxx
// get: return this._data.xxx
// set: this._data.xxx = val
proxy(vm, `_data`, key)
// 响应式处理
observe(data, true /* asRootData */)
}
Vue
实例挂载
const mount = Vue.prototype.$mount // 缓存 - runtime only 版本直接使用此 mount 方法
Vue.prototype.$mount = function() {} // 重新定义
// 分别判断 render 和 template
// 最终都会转化为 render function
const { render } = compileToFunctions()
vm.$options.render = render
// 调用原先原型上(即缓存的)$mount 方法进行挂载
return mount.call(this, el, hydrating) // hydrating 服务端渲染相关, 默认 false 即可
// 执行 mount 方法中的 mountComponent 方法
return mountComponent(this, el, hydrating)
// mountComponent 方法中主要做了两点
// 1. 调用 vm._render() 生成虚拟 node
const vnode = vm._render()
// 2. 实例化 watcher, 并调用 updateComponent 方法 - 初始化 & 数据变化均会执行
new Watcher(vm, updateComponent, noop, {}, true)
updateComponent = () => {
vm._update(vm._render(), hydrating) // 更新 dom
}
// 流程结束, 改变状态
vm._isMounted = true
callhook(vm, 'mounted')
render
函数
Vue.prototype._render = function() {
const vm = this
// 入口部分处理好的 render 函数
const { render } = vm.$options
// 核心部分 - vm.$createElement
vnode = render.call(vm._renderProxy, vm.$createElement)
return vnode
}
// vm.$createElement 方法的定义是在 initRender(vm) 中
// e.g. 在项目中使用 render 函数渲染的示例
import Vue from 'vue'
new Vue({
el: '#app',
// createElement -> vm.$createElement
render: function(createElement) {
return createElement('div', {
attrs: {
id: 'app'
}
}, this.message)
},
data() {
return {
message: 'hello, vue'
}
},
})
virtual dom
createElement
// 对 _createElement 的一层封装
export function createElement() {
return _createElement(context, tag, data, children, normalizationType)
}
// context - VNode 上下文环境: Component
// data - VNode 数据: VNodeData
// children - VNode 子节点: any - 需要被规范为标准的 VNode 数组
// normalizationType - 字节点规范类型 - 【主要】区分函数编译生成 || 用户手写
// src/core/instance/render.js
export function initRender(vm) {
vm._c(vm, a, b, c, d, false) // render functions complied from template.
vm.$createElement(vm, a, b, c, d, true) // user-written render functions.
}
// normalizationType 类别
// false - simpleNormalize
// true - alwaysNormalize
// 总结一下
// render functions 是编译生成 - false - simple - 调用 simpleNormalizeChildren
// render functions 是用户手写 - true - always - 调用 normalizeChildren
// src/core/vdom/helpers/nomalize-children.js
export function simpleNormalizeChildren(children) {
// 默认 VNode 类型, 但函数式组件返回的是数组而不是根结点
// 1-level deep
for(let i = 0; i < children.length; i++) {
if(Array.isArray(children[i])) {
return Array.prototype.concat.apply([], children)
}
}
return children
}
// 两个场景
// 1. 用户手写, children 只有一个节点, 创建简单的文本节点
// 2. <template>, <slot>, v-for 产生嵌套数组的情况
export function normalizeChildren(children) {
return isPrimitive(children) // 是否为基本类型
? [createTextVNode(children)] // 创建简单的文本节点
: Array.isArray(children)
? normalizeArrayChildren(children)
: undefined
}
export function normalizeArrayChildren(children) {
// 遍历 children
// 若子节点 c 为 Array, 则递归调用该方法
// 若子节点 c 为基础类型, 则调用 createTextVNode()
// opt: 若存在连续的 text 节点, 会把它们合并成一个 text 节点
}
// 创建 VNode
if(typeof tag === 'string') {
// 1. 内置节点
vnode = new VNode(parsePlatformTagName(tag), data, children, undefined, undefined, context)
// 2. 已注册组件名
vnode = createComponent(Ctor, data, context, children, tag)
// 3. 未知节点
vnode = new VNode(tag, data, children, undefined, undefined, context)
} else {
vnode = createComponent(tag, data, context, children)
}
return vnode
// vm._render() 过程至此完毕
update
Vue.prototype._update = function(vnode, hydrating) {
// 核心部分
vm.$el = vm.__patch__(vm.$el, vnode, hydrating, false)
}
// src/platforms/web/runtime/index.js
Vue.prototype.__patch__ = inBrowser ? patch : noop // 服务端渲染没有 dom 环境
// src/platforms/web/runtime/patch.js
export const patch = createPatchFuntion({nodeOps, modules }) // 真正执行的是 patch
// src/core/vdom/patch.js
export function createPatchFunction(backend) {
// 核心部分 - 通过虚拟节点创建真实的 DOM 并插入父节点
createElm() {
// 遍历子虚拟节点, 递归调用 - 深度优先
// 递归调用, 子元素优先 insert, 所以 vnode 树的插入顺序是先父后子
createChildren()
}
}
// 调用原生 DOM API 操作
function insert(parent, ele, ref) {
// e.g.
// insertBefore()
// appendChild()
}
发表评论
还没有评论,快来抢沙发吧!