入口文件
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._init
Vue.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
但是值为空,$root
Vue根实例。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的值变化的更新通知。
-