热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

Vue实例初始化过程

newVue()实例的初始化Vue.js是由原型链写法来实现的库,其构造函数在srccoreinstanceindex.jsfunctionVue(options){if(proc

new Vue() 实例的初始化

Vue.js 是由 原型链 写法来实现的库,其构造函数在 src/core/instance/index.js

function Vue(options) {
if (process.env.NODE_ENV !== ‘production‘ && !(this instanceof Vue)) {
warn(...)
// 必须是以 new Vue 方式来创建实例
}
this._init(options)
}
initMixin(Vue) // 定义了 _init 方法
stateMixin(Vue)
eventsMixin(Vue)
lifecycleMixin(Vue) // 定义了 _update 方法,$destroy 方法
renderMixin(Vue) // 定义了 _render 方法
function initMixin(Vue) {
Vue.prototype._init = function (options) {
const vm = this // 存储当前实例
...
vm._isVue = true // 确认自身为Vue实例
if (options && options._isComponent) {
initInternalComponent(vm, options) // 对于组件传入的options的合并
} else {
vm.$optiOns= mergeOptions(
resolveConstructorOptions(vm.constructor),
options || {},
vm
) // mergeOptions 将传入的options和构造函数的options合并到实例本身的$options
}
... // 一些实例自身的初始化(包括beforeCreate/created钩子的触发)
if (vm.$options.el) {
// 如果合并之后的选项中有 el,则将其进行挂载
vm.$mount(vm.$options.el)
}
}
}

在附带 compiler(编译器)的版本中,$mount 的实现方式如下

位置:src/platform/web/entry-runtime/with-compiler.js

const mount = Vue.prototype.$mount
// hydrating == false
Vue.prototype.$mount = function (el, hydrating) {
const optiOns= this.$options
if (!options.render) {
// 传入的options没有render函数,就通过template或者el,编译成render函数并赋值给 options.render
}
return mount.call(this, el, hydrating) // 调用原来的 $mount 方法, return vm
}
// src/platform/web/runtime/index.js 原$mount方法
Vue.prototype.$mount = function(el, hydrating) {
el = el && inBrowser ? query(el) : undefined // 通过传入的 el 来选择对应的容器元素
return mountComponent(this, el) // return vm
}

// src/core/instance/lifecycle.js (经简化)
function mountComponent(vm, el) {
vm.$el = el // 缓存 el
...
callHook(vm, ‘beoforeMount‘)
let updateCompOnent= () => {
vm._update(vm._render())
}
// 创建 vm 对应的一个 渲染Watcher
new Watcher(vm, updateComponent, noop, {
before() {
if (vm._isMounted && !vm._isDestroyed) {
callHook(vm, ‘beforeUpdate‘)
}
}
}, true /* 渲染watcher标志 */)

if (vm.$vnode == null) {
vm._isMounted = true
callHook(vm, ‘mounted‘)
}
return vm
}

创建 渲染Watcher 的过程

// src/core/observer/watcher.js
class Watcher {
constructor(vm, expOrFn, cb, options, isRenderWatcher) {
this.vm = vm // Vue 实例
if (isRenderWatcher) {
vm._watcher = this
}
vm._watchers.push(this)
...
if (typeof expOrFn === ‘function‘) {
this.getter = expOrFn // this.getter 其实就是定义Water时候传入的 updateComponent
} else {
...
}
this.value = this.lazy ? undefined : this.get() // this.lazy == false
// 所以在赋值 this.value 的过程中,this.get()执行过程中,传入的 getter 函数执行了一次
}

get() {
pushTarget(this)
let value
const vm = this.vm
try {
value = this.getter.call(vm, vm)
} catch (e) {
...
} finally {
if (this.deep) {
traverse(value)
}
popTarget()
this.cleanupDeps()
}
return value
}
}

分析 updateComponent 函数

// 定义
let updateCompOnent= () => {
vm._update(vm._render()) // 参数为 vnode
}
// 先分析 _render()
// src/core/instance/render.js
function renderMixin(Vue) {
Vue.prototype._render = function () {
const vm = this
const { render, _parentVnode } = vm.$options
if (_parentVnode) {
...
// 设置 vm 的插槽
}
vm.$vnode = _parentVnode // 存储 父vnode 节点
let vnode
try {
currentRenderingInstance = vm
vnode = render.call(vm._renderProxy, vm.$createElement)
// vm._renderProxy.render(vm.$createElement)
// vm._renderProxy == vm
} catch (e) {}
...
vnode.parent = _parentVnode
return vnode
// 返回一个由 编译/自带的 render 函数执行得到的 vnode
}
}

// render 函数的调用其实就是 $createElement 的执行
// 同一个文件中有这样的定义
// initRender 函数中
// 在 _init 方法初始化过程中,会调用initRender(vm)
vm.$createElement = (a,b,c,d) => createElement(vm,a,b,c,d,true)
// createElement 定义在 src/core/vdom/create-element.js
// src/core/vdom/create-element.js
function createElement(vm, tag, data, children, normalizationType, alwaysNormalize) {
if (Array.isArray(data) || isPrimitive(data)) {
// 参数重载
normalizatiOnType= children
children = data
data = undefined
}
return _createElement(context, tag, data, normalizationType) // vnode
}

function _createElement(context, tag, data, children, normalizationType) {
...
// 将children 拍平成一维数组
if (normalizatiOnType=== ALWAYS_NORMALIZE) {
children = normalizeChildren(children)
} else if (normalizatiOnType=== SIMPLE_NORMALIZE) {
children = simpleNormalizeChildren(children)
}

let vnode, ns
if (typeof tag === ‘string‘) {
// 适用于传入一个由 vue-loader 编译生成或者按格式编写 tag
if (config.isReservedTag(tag)) {
// 平台原有的 tag 标签,如H5的div等
vnode = new VNode(
config.parsePlatform(tag), data, children,
undefined, undefined, context
)
} else if (
(!data || !data.pre) &&
isDef(Ctor = resolveAssest(context.$options, ‘components‘ ,tag))
) {
// 创建组件的vnode
vnode = createComponent(Ctor, data, context, children, tag) // return vnode
}
} else {
// 适用于直接传入一个导出的 .vue 文件到render函数
// e.g. render: h => { return h(App) }
// 此处 h 相当于 createElement
// tag == App
vnode = createComponent(tag, data, context, children) // return vnode
}
if (Array.isArray(vnode)) {
return vnode
} else if (isDef(vnode)) {
...
return vnode
} else {
return createEmptyVNode() // 空白vnode
}
}
// 因此 vm._render() 函数会生成 vm 对应的 vnode 并返回给 vm._update 函数

// 分析 createComponent 函数
// src/core/vdom/create-component.js
function createComponent (Ctor, data, context, children, tag) {
if (isUndef(Ctor)) { return } // 没有传入构造函数/信息
const baseCtor = context.$options._base // 其实就是Vue,在合并选项的时候会合并进去
if (isObject(Ctor)) {
Ctor = baseCtor.extend(Ctor) // 传入的构造信息通过 Vue.extend 转为构造函数
}
...
installComponentHooks(data)
// 安装组件钩子,合并到data对象
/*
data = {
on: {...},
hook: {...}
}
*/
const name = Ctor.options.name || tag
const vnode = new VNode(
`vue-component-${Ctor.c_id}${name ? `-${name}` : ‘‘}`,
data ,undefined, undefined, undefined, context,
{ Ctor, propsData, listeners, tag, children }, // 该对象为componentOptions参数
asyncFactory
)
return vnode
}

再来看看 vm._update

// src/core/instance/lifecycle.js
function lifecycleMixin(Vue) {
Vue.prototype._update = function (vnode, hydrating) { // hydratng == false
const vm = this
const prevEl = vm.$el // 在 mountComponent 函数中缓存
const prevVnode = vm._vnode
vm._vnode = vnode // 缓存当前vnode 到实例的 _vnode属性
const restoreActiveInstance = setActiveInstance(vm)
// 存储当前的 vm 实例到 activeInstance
// 组件实际插入到 DOM 对象是在 __patch__ 过程中
if (!prevVnode) {
vm.$el = vm.__patch__(vm.$el, vnode, hydrating, false)
} else {
vm.$el = vm.__patch__(prevVnode, vnode)
}
restoreActiveInstance() // 重新释放当前 vm 实例,activeInstance 回退到上一个 vm 实例
...
// 根组件直接将 $el 更新到 父实例 的$el
// e.g. render: h => h(App) 最后会将 App 实例 patch 得到的 $el 更新到根实例(new Vue) 上
if (vm.$vnode && vm.$parent && vm.$vnode === vm.$parent._vnode) {
vm.$parent.$el = vm.$el
}
}
}
// 至此实例的渲染 watcher 创建完成,实例挂载结束

下面是组件如何 实际插入到DOM对象 的过程分析,在 patch 函数中,

// src/platforms/web/runtime/index.js
Vue.prototype.__patch__ = patch
// 这个patch 函数就是实际上调用的函数
// 经分析最后 patch 函数在 src/core/vdom/patch.js 的 createPatchFunction 中返回
// 简化版 patch
function patch (oldVnode, vnode, hydrating, removeOnly) {
if (isUndef(vnode)) {
// 只有传入了旧节点的信息
if (isDef(oldVnode)) invokeDestroy(oldVnode) // 销毁旧节点
}
let isInitialPatch = false // 是否为根节点
const insertedVnodeQueue = []
if (isUndef(oldVnode)) {
isInitialPatch = true
crateElm(vnode, insertedVnodeQueue)
} else {
const isRealElement = isDef(oldVnode.nodeType)
// h(App)是传入的 oldVnode 是 el
if (!isRealElement && sameVndoe(oldVnode, vnode)) {
patchVnode(oldVnode, vnode, insertedVnodeQueue)
} else {
if (isRealElement) {
...
oldVnode = emptyNodeAt(oldVnode) // 用 el 元素创建一个 oldVnode
}
const oldElm = oldVnode.elm // 就是传入的 el元素
const parentElm = nodeOps.parentNode(oldElm) // el的父元素

createElm(
vnode,
insertedVnodeQueue,
oldElm._leaveCB ? null : parentElm,
nodeOps.nextSibling(oldElm) // oldElm 的下一个兄弟节点
)
...
if (isDef(parentElm)) {
removeVnodes([oldVnode], 0, 0)
} else if (isDef(oldVnode.tag)) {
invokeDestroyHook(oldVnode)
}
}
}
invokeInsertHook(vnode, insertedVnodeQueue, isInitialPatch)
// 通过insert钩子触发组件的mounted钩子
return vnode.elm // elm元素 返回给__patch__ ,赋值给vm.$el
}

重点分析 createElm 函数

function createElm (
vnode,
insertedVnodeQueue,
parentElm,
refElm,
nested,
ownerArray,
index
) {
// vnode直接创建一个组件
if (createComponent(vnode, insertedVnodeQueue, parentElm, refElm)) {
return
}

const data = vnode.data
const children = vnode.children
const tag = vnode.tag
if (isDef(tag)) {
...
// 利用 vnode 的 tag 属性直接生成一个原生元素(div)
vnode.elm = nodeOps.createElement(tag, vnode)
}
createChildren(vnode, children, insertedVnodeQueue)
// 创建子节点,实际上也是调用 createElm
if (isDef(data)) {
// 插入vnode序列
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)
}

// createChildren
function createChildren (vnode, children, insertedVnodeQueue) {
if (Array.isArray(children)) {
// 只接受数组类型
if (process.env.NODE_ENV !== ‘production‘) {
checkDuplicateKeys(children)
}
for (let i = 0; i // 实际上将父 vnode 的 elm 作为父元素传入
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)) {
const isReactivated = isDef(vnode.componentInstance) && i.keepAlive
if (isDef(i = i.hook) && isDef(i = i.init)) {
i(vnode, false /* hydrating */)
// 调用hooks中的init钩子,在 create-component.js中componentVNodeHooks
}
if (isDef(vnode.componentInstance)) {
initComponent(vnode, insertedVnodeQueue)
insert(parentElm, vnode.elm, refElm) // 插入到父元素 DOM
if (isTrue(isReactivated)) {
reactivateComponent(vnode, insertedVnodeQueue, parentElm, refElm)
}
return true
}
}
}

// src/core/vdom/create-component.js
// init钩子函数
function init (vnode, hydrating) {
if (
vnode.componentInstance &&
!vnode.componentInstance._isDestroy &&
vnode.data.keepAlive
) {
...
} else {
// createComponentInstanceForVnode 函数返回一个vm实例
// 实际上是调用了 vnode 的componentOptions.Ctor 来构造子组件
const child = vnode.compOnentInstance= createComponentInstanceForVnode(
vnode,
activeInstance
)
// 上面生成的实例挂载(el是undefined)
child.$mount(hydrating ? vnode.elm : undefined, htdrating) // hydrating == false
}
}

// createComponentInstanceForVnode
// parent为当前的父vm,就是需要创建的vm的父vm == activeInstance
function createComponentInstanceForVnode (vnode, parent) {
const optiOns= {
_isComponent: true,
_parentVnode: vnode,
parent
}
...
return new vnode.componentOptions.Ctor(options)
// Sub 构造函数的实例化 return vm
// 重新走一次 _init 流程
}

new Vue - vm.mount - mountComponent(vm) - vm._render(vmVnode) - vm.componentOptions - vm._update(vmVnode) - patch - createElm(vmVnode) - createComponent(vmVnode) - vm.hook.init - new child - vm.componentInstance - child.mount- createComponent(vmVnode) - insert
new child - child.mount - mountComponent(child) - child._update(childVnode) - patch - createElm(childVnode) - createElement(childVnode) - insert(child)

Vue 实例初始化过程



推荐阅读
  • 本文介绍了lua语言中闭包的特性及其在模式匹配、日期处理、编译和模块化等方面的应用。lua中的闭包是严格遵循词法定界的第一类值,函数可以作为变量自由传递,也可以作为参数传递给其他函数。这些特性使得lua语言具有极大的灵活性,为程序开发带来了便利。 ... [详细]
  • GetWindowLong函数
    今天在看一个代码里头写了GetWindowLong(hwnd,0),我当时就有点费解,靠,上网搜索函数原型说明,死活找不到第 ... [详细]
  • Java序列化对象传给PHP的方法及原理解析
    本文介绍了Java序列化对象传给PHP的方法及原理,包括Java对象传递的方式、序列化的方式、PHP中的序列化用法介绍、Java是否能反序列化PHP的数据、Java序列化的原理以及解决Java序列化中的问题。同时还解释了序列化的概念和作用,以及代码执行序列化所需要的权限。最后指出,序列化会将对象实例的所有字段都进行序列化,使得数据能够被表示为实例的序列化数据,但只有能够解释该格式的代码才能够确定数据的内容。 ... [详细]
  • 本文分享了一个关于在C#中使用异步代码的问题,作者在控制台中运行时代码正常工作,但在Windows窗体中却无法正常工作。作者尝试搜索局域网上的主机,但在窗体中计数器没有减少。文章提供了相关的代码和解决思路。 ... [详细]
  • 本文介绍了使用Java实现大数乘法的分治算法,包括输入数据的处理、普通大数乘法的结果和Karatsuba大数乘法的结果。通过改变long类型可以适应不同范围的大数乘法计算。 ... [详细]
  • HDU 2372 El Dorado(DP)的最长上升子序列长度求解方法
    本文介绍了解决HDU 2372 El Dorado问题的一种动态规划方法,通过循环k的方式求解最长上升子序列的长度。具体实现过程包括初始化dp数组、读取数列、计算最长上升子序列长度等步骤。 ... [详细]
  • 本文讨论了在Windows 8上安装gvim中插件时出现的错误加载问题。作者将EasyMotion插件放在了正确的位置,但加载时却出现了错误。作者提供了下载链接和之前放置插件的位置,并列出了出现的错误信息。 ... [详细]
  • 本文介绍了C#中数据集DataSet对象的使用及相关方法详解,包括DataSet对象的概述、与数据关系对象的互联、Rows集合和Columns集合的组成,以及DataSet对象常用的方法之一——Merge方法的使用。通过本文的阅读,读者可以了解到DataSet对象在C#中的重要性和使用方法。 ... [详细]
  • 本文介绍了OC学习笔记中的@property和@synthesize,包括属性的定义和合成的使用方法。通过示例代码详细讲解了@property和@synthesize的作用和用法。 ... [详细]
  • [译]技术公司十年经验的职场生涯回顾
    本文是一位在技术公司工作十年的职场人士对自己职业生涯的总结回顾。她的职业规划与众不同,令人深思又有趣。其中涉及到的内容有机器学习、创新创业以及引用了女性主义者在TED演讲中的部分讲义。文章表达了对职业生涯的愿望和希望,认为人类有能力不断改善自己。 ... [详细]
  • 高质量SQL书写的30条建议
    本文提供了30条关于优化SQL的建议,包括避免使用select *,使用具体字段,以及使用limit 1等。这些建议是基于实际开发经验总结出来的,旨在帮助读者优化SQL查询。 ... [详细]
  • 本文介绍了指针的概念以及在函数调用时使用指针作为参数的情况。指针存放的是变量的地址,通过指针可以修改指针所指的变量的值。然而,如果想要修改指针的指向,就需要使用指针的引用。文章还通过一个简单的示例代码解释了指针的引用的使用方法,并思考了在修改指针的指向后,取指针的输出结果。 ... [详细]
  • 本文介绍了PE文件结构中的导出表的解析方法,包括获取区段头表、遍历查找所在的区段等步骤。通过该方法可以准确地解析PE文件中的导出表信息。 ... [详细]
  • CentOS 7部署KVM虚拟化环境之一架构介绍
    本文介绍了CentOS 7部署KVM虚拟化环境的架构,详细解释了虚拟化技术的概念和原理,包括全虚拟化和半虚拟化。同时介绍了虚拟机的概念和虚拟化软件的作用。 ... [详细]
  • 本文讨论了clone的fork与pthread_create创建线程的不同之处。进程是一个指令执行流及其执行环境,其执行环境是一个系统资源的集合。在调用系统调用fork创建一个进程时,子进程只是完全复制父进程的资源,这样得到的子进程独立于父进程,具有良好的并发性。但是二者之间的通讯需要通过专门的通讯机制,另外通过fork创建子进程系统开销很大。因此,在某些情况下,使用clone或pthread_create创建线程可能更加高效。 ... [详细]
author-avatar
甜蜜棉羽
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有