入口文件
vue-2.6.11\src\platforms\web\entry-runtime-with-compiler.js
-
从vue原型中拿出
$mount进行覆盖,// 17行 const mount = Vue.prototype.$mount -
解析模板相关选项
// 18行 Vue.prototype.$mount = function ( el?: string | Element,// 宿主元素 hydrating?: boolean ): Component { //获取真实DOM el = el && query(el) // 34行 // 先判断有没有render,render函数优先级最高 // 优先级:render > template > el if (!options.render) { let template = options.template // 再判断有没有template并解析 if (template) { if (typeof template === 'string') { if (template.charAt(0) === '#') { template = idToTemplate(template) /* istanbul ignore if */ if (process.env.NODE_ENV !== 'production' && !template) { } } } else if (template.nodeType) { template = template.innerHTML } else { return this } // 最后查看el选项 } else if (el) { template = getOuterHTML(el) } // 处理模板的方式,编译它,最终目标是render if (template) { /* istanbul ignore if */ if (process.env.NODE_ENV !== 'production' && config.performance && mark) { mark('compile') } const { render, staticRenderFns } = compileToFunctions(template, { outputSourceRange: process.env.NODE_ENV !== 'production', shouldDecodeNewlines, shouldDecodeNewlinesForHref, delimiters: options.delimiters, comments: options.comments }, this) // 重新复制给选项 options.render = render options.staticRenderFns = staticRenderFns /* istanbul ignore if */ if (process.env.NODE_ENV !== 'production' && config.performance && mark) { mark('compile end') measure(`vue ${this._name} compile`, 'compile', 'compile end') } } }
初始化挂载
vue-2.6.11\src\platforms\web\runtime\index.js
-
安装平台特有的补丁(patch)函数:做初始化和更新,为了实现跨平台,平台特有的操作。
// 33行 Vue.prototype.__patch__ = inBrowser ? patch : noop -
实现
$mount:初始化挂载。$mount('#app')=>mountComponent:render()=>vdom=>patch()=>dom=>appendChile()- 初始化时执行
$mount('#app'),在内部调用mountComponent,调用内部的render函数,核心目标把当前虚拟DOM树传给patch()函数变成真实DOM,真实DOM挂载到#app上。
// 36行 Vue.prototype.$mount = function ( el?: string | Element, hydrating?: boolean ): Component { el = el && inBrowser ? query(el) : undefined return mountComponent(this, el, hydrating) }
初始化全局API
vue-2.6.11\src\core\index.js
-
初始化全局API:
Vue.use,component,directive,filter,mixin,set,extend,delete// 6行 initGlobalAPI(Vue)
Vue的构造函数
vue-2.6.11\src\core\instance\index.js
-
构造函数:new Vue(options)
// 8行 function Vue (options) { // 初始化 this._init(options) } -
声明实例属性和方法,把构造函数传递进去,通过混入模式把原型加上
_init方法。initMixin(Vue) // 熟悉的其他梳理属性和方法,有下面这些混入 stateMixin(Vue) eventsMixin(Vue) lifecycleMixin(Vue) renderMixin(Vue)
初始化
vue-2.6.11\src\core\instance\init.js
-
原型挂载
_init实现原型方法,在任何实例都可使用vm._initVue.prototype._init = function (options?: Object) -
合并选项
// 30行 // 合并选项:new Vue传入的时用户配置,需要和系统配置合并 if (options && options._isComponent) { initInternalComponent(vm, options) } else { vm.$options = mergeOptions( resolveConstructorOptions(vm.constructor), options || {}, vm ) } -
初始化操作
// 52行 // 生命周期初始化 组件实例相关的属性初始化,$parent,$root,$children,$refs initLifecycle(vm) // 事件初始化 监听自定义事件 initEvents(vm) // 解析插槽,$slots,$scopeSlots,$createElement() initRender(vm) callHook(vm, 'beforeCreate')// beforeCreate生命周期 // 组件状态相关的数据操作 // inject/provide 注入祖辈传递下来的数据。 initInjections(vm) // // 把自己内部的状态进行响应式的处理,`props`,`methods`,`data`,`computed`,`watch` initState(vm) // 提供给后代,用来隔代传递参数 initProvide(vm) callHook(vm, 'created')// created生命周期 -
initEvents(vm)
- 为什么要从父组件找Listeners?
- 子组件上有事件选项,但是在父组件内声明的。
- 派发和监听者都是子组件本身
(this.$on(),this.$emit()); - 事件的真正监听者时子组件自己。
-
updateComponentListeners从父组件身上拿出事件给子组件。
export function initEvents (vm: Component) { vm._events = Object.create(null) vm._hasHookEvent = false // 找到父组件的选项 const listeners = vm.$options._parentListeners if (listeners) { updateComponentListeners(vm, listeners) } } - 为什么要从父组件找Listeners?
-
initRender(vm)
// 34行 // 这里的$createElement就是render(h) vm.$createElement = (a, b, c, d) => createElement(vm, a, b, c, d, true) -
如果设置了
el,自动执行$mount()if (vm.$options.el) { vm.$mount(vm.$options.el) }
Vue执行流程
进入
src\core\instance\index.js文件内的Vue函数,执行this._init(options)。-
再进入
src\core\instance\init.js文件内的initMixin函数。32行此时的
options还是用户设置的初始化选项,data,el等。-
先判断是否为
isComponent,若不是执行mergeOptions将用户设置和系统设置进行合并。- 合并完成后
vm.$options是合并后的结果,可使用全局组件等{components{KeepAlive,Transition,TransitionGroup},data,el,filters}
- 合并完成后
执行
initProxy进行代理设置。-
依次执行初始化操作,
initLifecycle(vm),vm新增属性$children,$parent,$refs但是值为空,$rootVue根实例。initEvents(vm),vm新增属性_events事件监听callHook(vm, 'beforeCreate')派发生命周期,所以此时不可以操作数据。-
initState(vm),数据的代理和响应式发生在这里,vm新增数据相应式,新增_data(响应式有_ob_),$data(非响应式)-
进入文件
src\core\instance\state.js,执行initState函数,如果有重名按处理顺序,谁先占用谁使用。if (opts.props) initProps(vm, opts.props)处理props。if (opts.methods) initMethods(vm, opts.methods)处理methods。-
if (opts.data) { initData(vm) } else {observe(vm._data = {}, true) }处理data。- 如果有
data执行initData(vm)。- 执行
initData函数,判断是函数还是对象再做相应处理。 - 校验命名冲突,根据
props和methods。 - 最后执行
observe(data, true)递归响应式处理。
- 执行
- 如果没有
data执行observe(vm._data = {}, true)。- 进入文件
core\observer\index.js - 获取
Ob实例,是否作为根数据。- 如果有
_ob_直接返回,ob = value.__ob__ - 如果没有则创建,
ob = new Observer(value)- 初始化时先给
data的值创建一个ob,结果对象就有几个ob。 - 创建
dep实例this.dep = new Dep():对象也需要dep,对象如果动态增减属性。 -
Observer区分对象还是数组。 - 判断类型,指定
ob实例。- 如果时数组执行数组响应式
this.observeArray(value)。
1. 进入文件src\core\observer\array.js。
2. 默认的7个方法不会通知更新,将原有7个方法拦截修改。
3. 获取数组原型,const arrayProto = Array.prototype。
4. 克隆一份新原型,export const arrayMethods = Object.create(arrayProto)。
5. 7个变更方法需要覆盖,const methodsToPatch = [ 'push', 'pop', 'shift','unshift', 'splice', 'sort', 'reverse']。因为这7个会改变数组,其他的返回新数组。
6. 遍历7个方法,每次拿出一个方法保存原始方法const original = arrayProto[method]开始覆盖。
7. 执行默认方法const result = original.apply(this, args)。
8. 对新加入的元素进行响应式if (inserted) ob.observeArray(inserted)。
9. 变更通知const ob = this.__ob__。
10.ob内部有dep,让dep通知更新ob.dep.notify()。
2. 如果是对象执行对象响应式this.walk(value),执行defineReactive(obj, keys[i])。
1. 每个key对应一个dep,const dep = new Dep()。
2. 依赖收集dep.depend(),vue2中一个组件是一个Watcher。
1.dep:n=>wtacher:1,多对一。
2. 通过diff算法比对变化。
3. 进入文件src\core\observer\dep.js
1.depend函数内执行的Dep.target.addDep(this),watcher.addDep()。
2. 进入文件src\core\observer\watcher.js的addDep函数。
1. 相互添加引用的过程。
2.watcher添加dep,this.newDepIds.add(id),this.newDeps.push(dep)
3.dep添加watcher,dep.addSub(this)。
3. 用户手动创建Watcher,this.$watch(key,cb)。
1.dep:n=>wtacher:n,多对多。
4. 子ob也要做依赖收集工作childOb.dep.depend()。
- 如果时数组执行数组响应式
- 初始化时先给
- 如果有
- 进入文件
- 如果有
if (opts.computed) initComputed(vm, opts.computed)处理computed。if (opts.watch && opts.watch !== nativeWatch) { initWatch(vm, opts.watch) }处理watch
-
执行挂载过程
vm.$mount(vm.$options.el)。
-
进入文件
src\platforms\web\runtime\index.js执行Vue.prototype.$mount挂载函数。- 执行
mountComponent(this, el, hydrating)将虚拟DOM转换成真实DOM。
- 执行
-
进入文件
src\core\instance\lifecycle.js- 执行
mountComponent函数内部,挂载callHook(vm, 'beforeMount')生命周期钩子。 - 声明
updateComponent组件更新函数函数未执行。-
vm._render()渲染函数。 -
vm._update()更新函数。
-
-
new Watcher内部会执行updateComponent,这次才真正执行updateComponent,执行函数内部的vm._render()渲染函数。- 进入文件
src\core\instance\render.js,执行Vue.prototype._render,渲染函数的目的是得到虚拟DOM。-
vnode = render.call(vm._renderProxy, vm.$createElement)获得虚拟DOM。
-
- 进入文件
- 再执行
lifecycle.js文件内执行vm._update()更新函数。- 判断是否有虚拟DOM。
- 若没有则通过
vm._patch_创建,虚拟DOM将变成真实DOM。 - 若有则通过
vm._patch_更新。
- 若没有则通过
- 判断是否有虚拟DOM。
- 执行

new Vue({})发生了什么?
- 选项的合并,用户选项和系统选项的合并。
- 组件实例相关的属性初始化,
$parent,$root,$children,$refs - 监听自定义事件。
- 解析插槽,
$slots,$scopeSlots,$createElement() - 注入祖辈传递下来的数据。
- 把自己内部的状态进行响应式的处理,
props,methods,data,computed,watch - 提供给后代,用来隔代传递参数
Vue数据响应式
- Vue中利用了JS语言特性
Object.defineProperty(),通过定义对象属性getter/setter拦截对属性的访问。
具体实现是在Vue初始化时,会调用initState,它会初始化data,props等,这里着重关注data初始
化。 - Vue1最大问题watcher过多,项目大到一定程度容易崩溃,所以导致Vue1性能很差。
- Vue2的解决方法,把watcher粒度降低,一个组件一个watcher。
- 如果有数据发生变化,通过Diff算法把两次计算的结果比较,才能知道那里变化并进行更新。
- Watcher和属性key之间的关系是1对N,正好跟Vue1的一对N是相反。
- 当用户自己编写表达式this.$watch('foo',function(){})时,会产生新的watcher实例,此时'foo'又跟多个watcer产生关系,N对N。
- 在依赖收集过程中相互添加关系,
watcher.addDep()。 - 一个对象一个Observer,内部有个大管家dep。
- 大管家负责对象新增或删除属性的更新通知。
- 一个key一个小管家dep。
-
小关节负责对应key的值变化的更新通知。
-
