浅尝vue
rollup 与 webpack
umd,esm,commonjs
weex
shared
export const ASSET_TYPES = [
'component',
'directive',
'filter'
]
export const LIFECYCLE_HOOKS = [
'beforeCreate',
'created',
'beforeMount',
'mounted',
'beforeUpdate',
'updated',
'beforeDestroy',
'destroyed',
'activated',
'deactivated',
'errorCaptured',
'serverPrefetch'
]
理一下代码的执行思路哈
// 代码必然从构造函数开始执行的。
<body>
<div id="app">
<span>
{{aa}}
</span>
</div>
<script>
new Vue({
data() {
return {
aa: 'aa'
}
}
}).$mount('#app')
</script>
</body>
【1.寻找Vue constructor阶段】
// 寻找的过程中顺便发现的
Vue.prototype.$mount
Vue.prototype.__patch__ = inBrowser ? patch : noop
initGlobalAPI(Vue) 中---->>>
initUse(Vue) Vue.use
initMixin(Vue) Vue.mixin
initExtend(Vue) Vue.extend
initAssetRegisters(Vue) Vue[type]
// 注意这句代码 this.options[type + 's'][id] = definition
【2.prototype预处理阶段】
找到了Vue构造器,但是发现除了上边的预处理,代码还做Vue原型prototype做了很多预处理,这个阶段称为prototype处理阶段
initMixin(Vue)
stateMixin(Vue)
eventsMixin(Vue)
lifecycleMixin(Vue)
renderMixin(Vue)
prototype处理阶段(1)initMixin
方法。
Vue.prototype._init = function () {
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')
}
prototype处理阶段(2)stateMixin
Object.defineProperty(Vue.prototype, '$data', dataDef)
Object.defineProperty(Vue.prototype, '$props', propsDef)
Vue.prototype.$set = set
Vue.prototype.$delete = del
Vue.prototype.$watch
prototype处理阶段(3)initRender
Vue.prototype.$on
Vue.prototype.$once
Vue.prototype.$off
Vue.prototype.$emit
prototype处理阶段(4)lifecycleMixin
Vue.prototype._update
Vue.prototype.$forceUpdate
Vue.prototype.$destroy
prototype处理阶段(5)renderMixin
Vue.prototype.$nextTick
Vue.prototype._render
【3.vm处理阶段】
以上是prototype的处理阶段,或者说new Vue之前的处理,那么new Vue之后做了什么,这个阶段可以称为(vm处理阶段)
也就是执行_init方法
vm处理阶段initLifecycle
parent.$children.push(vm)
vm.$parent = parent
vm.$root = parent ? parent.$root : vm
vm.$children = []
vm.$refs = {}
vm处理阶段 initEvents
把父组件绑定的event映射到子组件。
updateListeners
vm处理阶段 initRender
vm.$slots = resolveSlots(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)
vm处理阶段 initInjections
vm处理阶段 initState
处理 opts.data (核心observe(data)) 这一块Observer阶段我们稍后详细的看下。
处理 opts.computed
处理 opts.watch
vm处理阶段initProvide
【4.Observer阶段】
vue中我们最关心的是如何实现mvvm的,data和dom是如何进行绑定的。到底对opt.data做了什么?
Observer阶段之new Observer。
// 记录下observer阶段
class Observer{
this.dep = new Dep()
def(value, '__ob__', this)
如果是数组那么...
如果是其他那么walk...,然后执行defineReactive
}
Observer阶段之数组处理
对数组操作的7个方法进行劫持,先执行这7个方法原本的操作,然后进行this.__ob__.dep.notify()
const methodsToPatch = [
'push',
'pop',
'shift',
'unshift',
'splice',
'sort',
'reverse'
]
Observer阶段之defineReactive
遍历所有的key进行劫持。
每一个key 都会const dep = new Dep()
通过Object.defineProperty的get进行依赖收集,通过set进行触发更新。
export function defineReactive (
obj: Object,
key: string,
val: any,
customSetter?: ?Function,
shallow?: boolean
) {
const dep = new Dep()
const property = Object.getOwnPropertyDescriptor(obj, key)
if (property && property.configurable === false) {
return
}
// cater for pre-defined getter/setters
const getter = property && property.get
const setter = property && property.set
if ((!getter || setter) && arguments.length === 2) {
val = obj[key]
}
let childOb = !shallow && observe(val)
Object.defineProperty(obj, key, {
enumerable: true,
configurable: true,
get: function reactiveGetter () {
const value = getter ? getter.call(obj) : val
if (Dep.target) {
dep.depend()
if (childOb) {
childOb.dep.depend()
if (Array.isArray(value)) {
dependArray(value)
}
}
}
return value
},
set: function reactiveSetter (newVal) {
const value = getter ? getter.call(obj) : val
/* eslint-disable no-self-compare */
if (newVal === value || (newVal !== newVal && value !== value)) {
return
}
/* eslint-enable no-self-compare */
if (process.env.NODE_ENV !== 'production' && customSetter) {
customSetter()
}
// #7981: for accessor properties without setter
if (getter && !setter) return
if (setter) {
setter.call(obj, newVal)
} else {
val = newVal
}
childOb = !shallow && observe(newVal)
dep.notify()
}
})
}
其实到这里initState中的Observer阶段就完成了。有人说,我什么都么看到哈? 看后边的代码执行$mount
【5.mountComponent阶段】
执行 mountComponent(vm: Component,el: ?Element, hydrating?: boolean)
一个vue组件执行一次mountComponen注册一个watcher
二个vue就会执行两次。
mountComponent () {
updateComponent = () => {
vm._update(vm._render(), hydrating)
}
new Watcher(vm, updateComponent, noop, {
before () {
if (vm._isMounted && !vm._isDestroyed) {
callHook(vm, 'beforeUpdate')
}
}
}, true /* isRenderWatcher */)
return vm;
}
【6.dom diff和 dom更新阶段】
最终执行vm._update(vnode, hydrating)
dom打补丁,然后更新真实dom
vm._vnode = vnode
// 这里就是进行dom diff 然后打补丁的地方
vm.$el = vm.__patch__(vm.$el, vnode, hydrating, false /* removeOnly */)
【7.从数据更新到Dom更新过程】
数据更新后,在更新dom。问题来了: dom事件触发是如何绑定到dom上面的呢?
【8.dom事件触发数据更新过程】
我们发现_update中传入的dom虚拟节点已经是把数据处理过的。
所以从数据到dom的映射过程在_render完成的。
_render调用的是我们的render函数。
render函数通过compileToFunctions生成。compileToFunctions调用createCompiler函数。
三个步骤。parse,optimize,generate.
const ast = parse(template.trim(), options)
if (options.optimize !== false) {
optimize(ast, options)
}
const code = generate(ast, options)
return {
ast,
render: code.render,
staticRenderFns: code.staticRenderFns
}
这样子编译的过程就把我们的data属性编译到了vNode上边去,并且如果如果触发事件会更新vm的data。
这个过程就不详细说了。
dom diff的过程我们就不说了。
看一下vue 的生命周期哪里执行的。
vue的指令的编译方式。
ASSET_TYPES.forEach(type => {
Vue[type] = function (
id: string,
definition: Function | Object
): Function | Object | void {
if (!definition) {
return this.options[type + 's'][id]
} else {
/* istanbul ignore if */
if (process.env.NODE_ENV !== 'production' && type === 'component') {
validateComponentName(id)
}
if (type === 'component' && isPlainObject(definition)) {
definition.name = definition.name || id
definition = this.options._base.extend(definition)
}
if (type === 'directive' && typeof definition === 'function') {
definition = { bind: definition, update: definition }
}
this.options[type + 's'][id] = definition
return definition
}
}
})
function genDirectives (el: ASTElement, state: CodegenState): string | void {
const dirs = el.directives
if (!dirs) return
let res = 'directives:['
let hasRuntime = false
let i, l, dir, needRuntime
for (i = 0, l = dirs.length; i < l; i++) {
dir = dirs[i]
needRuntime = true
const gen: DirectiveFunction = state.directives[dir.name]
if (gen) {
// compile-time directive that manipulates AST.
// returns true if it also needs a runtime counterpart.
needRuntime = !!gen(el, dir, state.warn)
}
if (needRuntime) {
hasRuntime = true
res += `{name:"${dir.name}",rawName:"${dir.rawName}"${
dir.value ? `,value:(${dir.value}),expression:${JSON.stringify(dir.value)}` : ''
}${
dir.arg ? `,arg:${dir.isDynamicArg ? dir.arg : `"${dir.arg}"`}` : ''
}${
dir.modifiers ? `,modifiers:${JSON.stringify(dir.modifiers)}` : ''
}},`
}
}
if (hasRuntime) {
return res.slice(0, -1) + ']'
}
}