前言
本文是系列开篇,系列的主旨在于分享自己在阅读vue源码时的收获和体会,一方面是让自己有个总结,另一方面帮助想要理解vue源码的同学有个可以参考的东西。
写文章的时候vue版本为2.4.2
开篇
我们来看一下官网的例子这是最简单的vue的使用实例,本系列从这个实例作为开始来一步一步解析vue2的源码。本篇就先对Vue构造函数做了一个简单的解析。
分析
分析项目结构
这个项目结构我以后每一篇都会把那一篇需要的都会再说一遍,所以不用急着一步到位的了解所有文件夹的用处。
├── src ----------------------------------- 这个是我们最应该关注的目录,包含了源码
│ ├── entries --------------------------- 包含了不同的构建或包的入口文件
│ │ ├── web-runtime.js
│ │ ├── web-runtime-with-compiler.js
│ │ ├── web-compiler.js
│ │ ├── web-server-renderer.js
│ ├── compiler
│ │ ├── parser ------------------------ 存放将模板字符串转换成元素抽象语法树的代码
│ │ ├── codegen ----------------------- 存放从抽象语法树(AST)生成render函数的代码
│ │ ├── optimizer.js ------------------ 分析静态树,优化vdom渲染
│ ├── core ------------------------------ 存放通用的,平台无关的代码
│ │ ├── observer
│ │ ├── vdom
│ │ ├── instance ---------------------- 包含Vue构造函数设计相关的代码
│ │ ├── global-api -------------------- 包含给Vue构造函数挂载全局方法(静态方法)或属性的代码
│ │ ├── components
│ ├── server
│ ├── platforms
│ ├── sfc
│ ├── shared
Vue构造函数
我们先去找找Vue构造函数在哪吧,之前的项目结构里面我们也可以看到core文件夹有个instance文件夹。这里面就是构造函数的定义。
看看index.js的代码
index.js
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')
}
this._init(options)
}
initMixin(Vue)
stateMixin(Vue)
eventsMixin(Vue)
lifecycleMixin(Vue)
renderMixin(Vue)
export default Vue
这里有个值得一提的地方,我们看到Vue构造函数里面有一句warn('Vue is a constructor and should be called with the 'new' keyword')
这里开篇我们就不细看了,这里主要是为了检测是不是使用的构造函数方式还是直接以函数的方式调用的。
然后options被传进了Vue原型里面的_init方法里面。options回顾一下就是之前的
init.js
这个文件里面主要内容是为Vue原型挂载_init方法
init方法里面的proxy还有内部主键啥的我们都先不管,看看下面这部分代码
...
// 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)
}
...
现在大家可以回忆一下vue文档里面关于生命周期那一部分的图
基本上上面这段代码涵盖了new Vue()一直到created钩子后判断options里面是否有el属性。
我们一句一句看。
initLifecycle
lifecycle.js里面的其中一个函数,基本上是初始化生命周期的一些变量,refs也是这个阶段初始化的,这个我们后面会有一章单独讲生命周期。
export function initLifecycle (vm: Component) {
const options = vm.$options
// locate first non-abstract parent
let parent = options.parent
if (parent && !options.abstract) {
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
}
initEvents
event.js其中一个函数,暂时我还没读完这一部分的代码,不是很懂具体是干什么的,我系列写完会回来重新补充的。
export 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)
}
}
这里有一个值得一提的地方,看到vm._events = Object.create(null)
,我们控制台可以输入一下看一下Object.create(null)
结果是什么。
我一开始有点疑惑,这和对象字面量有啥区别,不过我又试了下知道了
我也google了一下,stackoverflow里面也有人问了类似的问题。Creating Js object with Object.create(null)? 反正这个方式创建的对象以null为原型创建一个对象,没有任何属性。
然而typeof null为object,可null又不可能是个对象,也没proto指针,很神奇的东西。
initRender
render.js中的一个函数,$slots在这里初始化的,还有一些我没看懂,后面补充。
export function initRender (vm: Component) {
vm._vnode = null // the root of the child tree
vm._staticTrees = null
const parentVnode = vm.$vnode = vm.$options._parentVnode // the placeholder node in parent tree
const renderContext = parentVnode && parentVnode.context
vm.$slots = resolveSlots(vm.$options._renderChildren, renderContext)
vm.$scopedSlots = emptyObject
// bind the createElement fn to this instance
// so that we get proper render context inside it.
// args order: tag, data, children, normalizationType, alwaysNormalize
// internal version is used by render functions compiled from templates
vm._c = (a, b, c, d) => createElement(vm, a, b, c, d, false)
// normalization is always applied for the public version, used in
// user-written render functions.
vm.$createElement = (a, b, c, d) => createElement(vm, a, b, c, d, true)
// $attrs & $listeners are exposed for easier HOC creation.
// they need to be reactive so that HOCs using them are always updated
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)
}
}
initInjections
inject.js的一个函数,这个我也没看,后面补充。。。
export 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
}
}
initState
state.js的一个函数,可以看到props,methods,data,computed,watch都是这个时候初始化的。
export 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)
}
}
initProvide
也是inject.js里面的一个函数,这个也后面补充吧。。
export function initProvide (vm: Component) {
const provide = vm.$options.provide
if (provide) {
vm._provided = typeof provide === 'function'
? provide.call(vm)
: provide
}
}
后记
第一章还是只是介绍Vue构造函数并说了比较简单的东西,没深入,下一节讲一下vue的生命周期钩子实现。