new Vue(options)主要做了 this._init()
可以看看_init()做了什么?
1、来自于哪里? 来自initMixn中,对Vue.prototype._init()
2 整个流程
import { initMixin } from './init'
import { stateMixin } from './state'
import { renderMixin } from './render'
import { eventsMixin } from './events'
import { lifecycleMixin } from './lifecycle'
import { warn } from '../util/index'
function Vue(options) {
if (process.env.NODE_ENV !== 'production' &&
!(this instanceof Vue)
) {
warn('Vue is a constructor and should be called with the `new` keyword')
}
// new Vue的时候主要执行这个_init函数
this._init(options)
}
initMixin(Vue) // _init函数来自于这里
stateMixin(Vue)
eventsMixin(Vue)
lifecycleMixin(Vue)
renderMixin(Vue)
export default Vue
Vue.prototype._init = function (options) {
let vm = this;
// 每个vue都会有一个uid
vm._uid = uid++
// 把一些全局的api方法混入到当前实例的$options上面
vm.$options = mergeOptions(vm.constructor.options, options)
//生命周期钩子beforeCreate
callHook(vm, 'beforeCreate')
//初始化状态,props,methods,data,computed,watch
initState(vm)
//初始化成功后调用created钩子
callHook(vm, 'created')
if (vm.$options.el) {
//开始挂载
vm.$mount(vm.$options.el)
}
}
Vue.prototype.$mount = function (el) {
//根据用户传入的 el 属性获取节点
el = el && document.querySelector(el);
let vm = this;
//把节点放在 vm.$el 上方便后面使用
vm.$el = el;
let options = vm.$options;
let template
/**
* 编译权重:
* 优先看有没有render函数,如果有直接用
* 如果没有render函数就看有没有template模板
* 如果都没有就直接获取el的outerHTML作为渲染模板
*/
if (!options.render) {
if (!options.template) {
template = el.outerHTML
} else {
template = vm.$options.template
}
}
if (template) {
//用 template 生成 render 函数
let render = compileToFunctions(template)
options.render = render
}
//调用 mount 方法开始渲染页面。
return mount(this, el)
}
export function mountComponent(vm, el) {
//渲染之前调用 beforeMount 生命周期
callHook(vm, 'beforeMount')
//创建一个更新渲染函数 ( 用来得到 Vnode 渲染真实 dom )
let updateComponent = () => {
vm.update(vm._render())
}
//生成一个渲染 watcher 每次页面依赖的数据更新后会调用 updateComponent 进行渲染
new Watcher(vm, updateComponent, () => {},{
before () {
callHook(vm, 'beforeUpdate')
}
},true)
//渲染真实 dom 结束后调用 mounted 生命周期
callHook(vm, 'mounted')
}
这里就开始真正渲染真实DOM,创建了Watcher,实例化water内部调用了updateComponent
Watcher内部做了什么?
export class Watcher {
constructor(vm,expOrFn,cb,options) {
if (typeof expOrFn === 'function') {
// 保留 updateComponent 方法
this.getters = expOrFn
}
this.get();
}
get() {
pushTarget(this)
let value
// 这里调用了 updateComponent 方法
value = this.getters.call(this.vm, this.vm);
popTarget()
return value
}
}
vue 初次渲染时 watcher 内部调用了 updateComponent 方法
updateComponent 做了什么?
let updateComponent = () => {
//获取到虚拟 dom 调用 update 进行渲染
vm.update(vm._render())
}
Vue.prototype._render = function () {
let vm = this
// 拿到 render 函数
let render = vm.$options.render;
// 调用 render 函数得到 Vnode
return render.call(vm)
}
Vue.prototype.update = function (vnode) {
let vm = this
// 获取到上一次的 Vnode 用于 diff 对比
const prevVnode = vm._vnode
if (!prevVnode) {
//首次渲染走这里
vm.$el = patch(vm.$el, vnode)
} else {
//数据更新驱动视图更新走这里
vm.$el = patch(prevVnode, vnode)
}
//保留 Vnode
vm._vnode = vnode
}
return function patch(el, vnode, hydrating, removeOnly) {
//首次渲染使用 Vnode 创建真实 dom
createElm(vnode, false, el)
return vnode.elm
}
function createElm (
vnode, //虚拟dom
insertedVnodeQueue,
parentElm, //父节点
) {
// 查看元素 tag 是不是组件,如果是组件就创建组件
if (createComponent(vnode, insertedVnodeQueue, parentElm)) {
return
}
const data = vnode.data //得到 data 数据
const children = vnode.children //得到子元素
const tag = vnode.tag //获取标签名
vnode.elm = document.createElement(tag)
if (isDef(tag)) {
//如果有子节点递归渲染子节点
createChildren(vnode, children, insertedVnodeQueue)
//给父元素插入子元素
parentElm.appendChild(elm)
} else if (isTrue(vnode.isComment)) {
//创建注释节点
vnode.elm = document.createComment(vnode.text)
//给父元素插入注释节点
parentElm.appendChild(elm)
} else {
//创建文本节点
vnode.elm = document.createTextNode(vnode.text)
//给父元素插入文本节点
parentElm.appendChild(elm)
}
}
function createChildren (vnode, children, insertedVnodeQueue) {
if (Array.isArray(children)) {
for (let i = 0; i < children.length; ++i) {
//渲染子节点
createElm(children[i], insertedVnodeQueue, vnode.elm)
}
}
}