Vue源码学习02 初始化模块init.js
接上篇,我们看到了VUE分了很多模块(initMixin()stateMixin()eventsMixin()lifecycleMixin()renderMixin()),通过使用Mixin模式,都是使用了JavaScript原型继承的原理,在Vue的原型上面增加属性和方法。我们继续跟着this._init(options)走,这个一点击进去就知道了是进入了init.js文件是在initMixin函数里面给Vue原型添加的_init方法。首先来从宏观看看这个init文件,可以看出主要是导出了两个函数:initMixin和resolveConstructorOptions,具体作用我们一步步来讨论。咋的一看这个文件,可能有些童鞋会看不明白函数参数括号里面写的是什么鬼,这个其实是应用了flow的类型检查,具体flow的使用这里就不介绍了,有兴趣的请移步:https://flow.org/en/
以下是截图说明

我们现在来看第一个函数initMixin,Vue实例在初始化的时候就调用了这个函数,
let uid = 0
export function initMixin (Vue: Class<Component>) {
Vue.prototype._init = function (options?: Object) {
const vm: Component = this
// a uid
vm._uid = uid++
let startTag, endTag
/* istanbul ignore if */ 【**注:istanbul 是代码覆盖率检测工具,此注释为代码测试用**】
if (process.env.NODE_ENV !== 'production' && config.performance && mark) {
startTag = `vue-perf-init:${vm._uid}`
endTag = `vue-perf-end:${vm._uid}`
mark(startTag)
}
// a flag to avoid this being observed
vm._isVue = true
// merge options
if (options && options._isComponent) {
// optimize internal component instantiation
// since dynamic options merging is pretty slow, and none of the
// internal component options needs special treatment.
initInternalComponent(vm, options)
} else {
vm.$options = mergeOptions(
resolveConstructorOptions(vm.constructor),
options || {},
vm
)
}
/* istanbul ignore else */
if (process.env.NODE_ENV !== 'production') {
initProxy(vm)
} else {
vm._renderProxy = vm
}
// expose real self
vm._self = vm
initLifecycle(vm)
initEvents(vm)
initRender(vm)
callHook(vm, 'beforeCreate')
initInjections(vm) // resolve injections before data/props
initState(vm)
initProvide(vm) // resolve provide after data/props
callHook(vm, 'created')
/* istanbul ignore if */
if (process.env.NODE_ENV !== 'production' && config.performance && mark) {
vm._name = formatComponentName(vm, false)
mark(endTag)
measure(`${vm._name} init`, startTag, endTag)
}
if (vm.$options.el) {
vm.$mount(vm.$options.el)
}
}
}
我们本着宏观简化原则,这个函数里面前面有三个if判断工作我们可以先不细化讨论【有兴趣可移步:http://www.cnblogs.com/QH-Jimmy/p/6862539.html】,大致第一个是用performance做性能监测,第二个合并option,第三个是做代理拦截,是ES6新特性,可参考阮一峰大神关于proxy的介绍【http://es6.ruanyifeng.com/#docs/proxy】。那么就进入了初始化函数主要点:
initLifecycle(vm) //生命周期变量初始化
initEvents(vm) //事件监听初始化
initRender(vm) //初始化渲染
callHook(vm, 'beforeCreate') //回调钩子beforeCreate
initInjections(vm) //初始化注入
initState(vm) // prop/data/computed/method/watch状态初始化
initProvide(vm) // resolve provide after data/props
callHook(vm, 'created') //回调钩子created
/* istanbul ignore if */
if (process.env.NODE_ENV !== 'production' && config.performance && mark) {
vm._name = formatComponentName(vm, false)
mark(endTag)
measure(`${vm._name} init`, startTag, endTag)
}
if (vm.$options.el) {
vm.$mount(vm.$options.el)
}
———————————这里来一个插曲start——————————
【V2.1.8及以前的版本】这里比较方便理解在生命周期created之后再做render,那么在created之前就无法获取DOM。这也是在有些源码解析文章里面很容易见到的分析,也是正确的
initLifecycle(vm)
initEvents(vm)
callHook(vm, 'beforeCreate')
initState(vm)
callHook(vm, 'created')
initRender(vm)
【v2.1.9及以后的版本】但到这里一开始就懵逼了很久render提到beforeCreate之前去了,那岂不是DOM在beforeCreate之前就能获取到了?显然不对了,请注意render虽然提前了,但是后面多了一个if这个if里面才获取DOM的关键,这个if在2.1.8版本之前是在render函数里面的,在2.1.9之后被提出来,然后render函数提前了,至于为何提前暂未了解,此处只是踩了一个看其他源码解析不同版本带来的坑!
initLifecycle(vm)
initEvents(vm)
initRender(vm)
callHook(vm, 'beforeCreate')
initState(vm)
callHook(vm, 'created')
if (vm.$options.el) {
vm.$mount(vm.$options.el)
}
———————————插曲end,继续————————————
1.initLifecycle
function initLifecycle (vm: Component) {
const options = vm.$options
// locate first non-abstract parent
let parent = options.parent //我理解为父实例或者父组件
if (parent && !options.abstract) { //例子中没有parent,断点代码的时候自动跳过
while (parent.$options.abstract && parent.$parent) {
parent = parent.$parent
}
parent.$children.push(vm)
}
vm.$parent = parent
vm.$root = parent ? parent.$root : vm
vm.$children = []
vm.$refs = {}
vm._watcher = null
vm._inactive = null
vm._directInactive = false
vm._isMounted = false
vm._isDestroyed = false
vm._isBeingDestroyed = false
}
这个函数主要是有父实例的情况下处理vm.$parent和vm.$children这俩个实例属性,我此处没有就跳过,其他的就是新增了一些实例属性
2.initEvents
function initEvents (vm: Component) {
vm._events = Object.create(null)
vm._hasHookEvent = false
// init parent attached events
const listeners = vm.$options._parentListeners
if (listeners) {
updateComponentListeners(vm, listeners)
}
}
又新增两个属性,后面那个if条件里面是有父组件的事件时初始化,估计就是props和events父子组件通信的事件内容。
3.initRender
function initRender (vm: Component) {
vm._vnode = null // the root of the child tree
vm._staticTrees = null
const parentVnode = vm.$vnode = vm.$options._parentVnode
const renderContext = parentVnode && parentVnode.context
vm.$slots = resolveSlots(vm.$options._renderChildren, renderContext)
vm.$scopedSlots = emptyObject
vm._c = (a, b, c, d) => createElement(vm, a, b, c, d, false)
vm.$createElement = (a, b, c, d) => createElement(vm, a, b, c, d, true)
const parentData = parentVnode && parentVnode.data
/* istanbul ignore else */
if (process.env.NODE_ENV !== 'production') {
defineReactive(vm, '$attrs', parentData && parentData.attrs, () => {
!isUpdatingChildComponent && warn(`$attrs is readonly.`, vm)
}, true)
defineReactive(vm, '$listeners', vm.$options._parentListeners, () => {
!isUpdatingChildComponent && warn(`$listeners is readonly.`, vm)
}, true)
} else {
defineReactive(vm, '$attrs', parentData && parentData.attrs, null, true)
defineReactive(vm, '$listeners', vm.$options._parentListeners, null, true)
}
}
此函数也是初始化了节点属性信息,绑定createElement函数到实例【并未挂载】,接下来调用beforeCreate回调钩子;——TODO1:后续专题分析VUE渲染逻辑
4.initInjections
function initInjections (vm: Component) {
const result = resolveInject(vm.$options.inject, vm)
if (result) {
observerState.shouldConvert = false
Object.keys(result).forEach(key => {
/* istanbul ignore else */
if (process.env.NODE_ENV !== 'production') {
defineReactive(vm, key, result[key], () => {
warn(
`Avoid mutating an injected value directly since the changes will be ` +
`overwritten whenever the provided component re-renders. ` +
`injection being mutated: "${key}"`,
vm
)
})
} else {
defineReactive(vm, key, result[key])
}
})
observerState.shouldConvert = true
}
}
此函数也是当有inject属性时做处理,源码例子无inject断点跑暂时跳过
5.initState
function initState (vm: Component) {
vm._watchers = []
const opts = vm.$options
if (opts.props) initProps(vm, opts.props)
if (opts.methods) initMethods(vm, opts.methods)
if (opts.data) {
initData(vm)
} else {
observe(vm._data = {}, true /* asRootData */)
}
if (opts.computed) initComputed(vm, opts.computed)
if (opts.watch && opts.watch !== nativeWatch) {
initWatch(vm, opts.watch)
}
}
可以看出此处是对options传入的props/methods/data/computed/watch属性做初始化————TODO2:分析每个属性的初始化
6.initProvide
function initProvide (vm: Component) {
const provide = vm.$options.provide
if (provide) {
vm._provided = typeof provide === 'function'
? provide.call(vm)
: provide
}
}
这个函数跟4.initInjections在同一个inject.js中,也是在传入参数有provide属性时做处理,暂时跳过,然后就到了created回调钩子,最后的vm.$mount接入TODO1;
今天initMixin到此结束,下篇继续TODO1~
Vue源码学习02 初始化模块init.js的更多相关文章
- Vue源码学习1——Vue构造函数
Vue源码学习1--Vue构造函数 这是我第一次正式阅读大型框架源码,刚开始的时候完全不知道该如何入手.Vue源码clone下来之后这么多文件夹,Vue的这么多方法和概念都在哪,完全没有头绪.现在也只 ...
- Vue源码学习二 ———— Vue原型对象包装
Vue原型对象的包装 在Vue官网直接通过 script 标签导入的 Vue包是 umd模块的形式.在使用前都通过 new Vue({}).记录一下 Vue构造函数的包装. 在 src/core/in ...
- Vue源码探究-状态初始化
Vue源码探究-状态初始化 Vue源码探究-源码文件组织 Vue源码探究-虚拟DOM的渲染 本篇代码位于vue/src/core/instance/state.js 继续随着核心类的初始化展开探索其他 ...
- 【Vue源码学习】依赖收集
前面我们学习了vue的响应式原理,我们知道了vue2底层是通过Object.defineProperty来实现数据响应式的,但是单有这个还不够,我们在data中定义的数据可能没有用于模版渲染,修改这些 ...
- Vue源码学习三 ———— Vue构造函数包装
Vue源码学习二 是对Vue的原型对象的包装,最后从Vue的出生文件导出了 Vue这个构造函数 来到 src/core/index.js 代码是: import Vue from './instanc ...
- 最新 Vue 源码学习笔记
最新 Vue 源码学习笔记 v2.x.x & v3.x.x 框架架构 核心算法 设计模式 编码风格 项目结构 为什么出现 解决了什么问题 有哪些应用场景 v2.x.x & v3.x.x ...
- VUE 源码学习01 源码入口
VUE[version:2.4.1] Vue项目做了不少,最近在学习设计模式与Vue源码,记录一下自己的脚印!共勉!注:此处源码学习方式为先了解其大模块,从宏观再去到微观学习,以免一开始就研究细节然后 ...
- 【Vue源码学习】响应式原理探秘
最近准备开启Vue的源码学习,并且每一个Vue的重要知识点都会记录下来.我们知道Vue的核心理念是数据驱动视图,所有操作都只需要在数据层做处理,不必关心视图层的操作.这里先来学习Vue的响应式原理,V ...
- Vue 源码学习(1)
概述 我在闲暇时间学习了一下 Vue 的源码,有一些心得,现在把它们分享给大家. 这个分享只是 Vue源码系列 的第一篇,主要讲述了如下内容: 寻找入口文件 在打包的过程中 Vue 发生了什么变化 在 ...
随机推荐
- 深入理解计算机系统(2.7)------二进制小数和IEEE浮点标准
整数的表示和运算我们已经讲完了,在实际应用中,整数能够解决我们大部分问题.但是某些需要精确表示的数,比如某件商品的价格,某两地之间的距离等等,我们如果用整数表示将会有很大的出入,这时候浮点数就产生了. ...
- websocket的理解及实例应用
websocket协议是HTML5提出的一个新的规范,主要用于实现服务器及时推送信息给客户端的功能. websocket实现是基于HTTP协议的部分握手功能,但是websocket仅仅握手一次即可进行 ...
- 替换应用程序DLL动态库的详细方法步骤 (gts.dll为例)
在C++ builder编译器IDE软件下 1.View -Project Manageer --找到需要替换的x.dll(gts.dll)对应的x.lib(gts.lib),然后Remove2.Pr ...
- 批处理之 for/f 详解
含有/F的for格式:FOR /F ["options"] %%i IN (file) DO command FOR /F ["options"] %%i IN ...
- 洗礼灵魂,修炼python(6)--活起来的代码+列表
活起来的用法: 使用input内置函数 注意python2中和python3中,input函数是不太一样的,python2中,input用户传入什么类型就是什么类型而python3中,不管传入什么类型 ...
- 天上掉Pizza
天上掉Pizza 时间限制: 3 Sec 内存限制: 128 MB提交: 73 解决: 48[提交][状态][讨论版] 题目描述 明明喜欢Pizza,但总是缺钱.有一天,他在报纸上阅读,他最喜爱的 ...
- qplot函数用法(转载)
http://blog.csdn.net/u014801157/article/details/24372499 写的很全面 放在这里记录下
- 前端基础之JavaScript
什么是JavaScript? JavaScript,也称ECMAScript,是一种基于对象和事件驱动并具有相对安全性并广泛用于客户端网页开发的脚本语言,同时也是一种广泛用于客户端Web开发的脚本语言 ...
- webpack2使用ch1-目录说明
1 目录解释 webpack.config.js:配置文件,配置文件可以改成其他名,但package.json --config文件名称也要对应修改 2 webpack.config.js //we ...
- iOS音频播放、录音、视频播放、拍照、视频录制
随着移动互联网的发展,如今的手机早已不是打电话.发短信那么简单了,播放音乐.视频.录音.拍照等都是很常用的功能.在iOS中对于多媒体的支持是非常强大的,无论是音视频播放.录制,还是对麦克风.摄像头的操 ...