残缺代码 (https://github.com/mfaying/si...
初始化阶段
new Vue()到created之间的阶段叫作初始化阶段,这个阶段的次要目标是在Vue.js实例上初始化一些属性、事件以及响应式数据。如props、methods、data、computed、watch、provide和inject
模板编译阶段
在created钩子函数与beforeMout钩子函数之间的阶段是模板编译阶段。
这个阶段的目标是将模板编译为渲染函数,只存在于完整版中。
挂载阶段
beforeMount钩子函数到mounted钩子函数之间是挂载阶段。
在这个阶段,Vue.js会将其实例挂载到DOM元素上,艰深地讲,就是将模板渲染到指定的DOM元素中。在挂载的过程中,Vue.js会开启Watcher来继续追踪依赖的变动。
当数据(状态)发生变化时,Watcher会告诉虚构DOM从新渲染视图,并且会在渲染视图前触发beforeUpdate钩子函数,渲染结束后触发updated钩子函数。
卸载阶段
利用调用vm.$destroy办法后,Vue.js的生命周期会进入卸载阶段。
在这个阶段,Vue.js会将本身从父组件中删除,勾销实例上所有依赖的追踪并且移除所有的事件监听器。
卸载阶段
原理就是vm.$destroy办法的外部原理
模板编译阶段和挂载阶段
也是后面介绍过的。
new Vue()被调用时产生了什么
当new Vue()被调用时,会首先进行一些初始化操作,而后进入模板编译阶段,最初进入挂载阶段。
其具体实现是这样的
function Vue(options) { this._init(options);}
this._init(options)执行了生命周期的初始化流程。
_init办法的外部原理
export function initMixin(Vue) { Vue.prototype._init = function(options) { vm.$options = mergeOptions( // 获取以后实例中构造函数的options及其所有父级实例构造函数的options resolveConstructorOptions(vm.constructor), options || {}, vm ) // 初始化lifecycle initLifecycle(vm); // 初始化事件 initEvents(vm); initRender(vm); callHook(vm, 'beforeCreate') // 初始化inject initInjections(vm); // 在 data/props前初始化inject // 初始化状态,这里的状态指的是props、methods、data、computed以及watch iniiState(vm); // 初始化provide initProvide(vm); // 在 data/props后初始化provide callHook(vm, 'created') // 如果有el选项,则主动开启模板编译阶段与挂载阶段 // 如果没有传递el选项,则不进入下一个生命周期流程 // 用户须要执行vm.$mount办法,手动开启模板编译阶段与挂载阶段 if (vm.$options.el) { vm.$mount(vm.$options.el) } };}
callHook函数的外部原理
Vue.js通过callHook函数来触发生命周期钩子
Vue.js在合并options的过程中会找出options中所有key是否钩子函数的名字,并将它转换成数组。数组因为Vue.mixin办法,同一生命周期,会执行多个同名生命周期办法。
上面列出了所有生命周期钩子的函数名
- beforeCreate
- created
- beforeMount
- mounted
- beforeUpdate
- updated
- beforeDestroy
- destroyed
- activated
- deactivated
- errorCaptured
实现代码如下:
export function callHook(vm, hook) { const handlers = vm.$options[hook]; if (handlers) { for (let i = 0, j = handlers.length; i < j; i++) { try { handlers[i].call(vm); } catch (e) { handleError(e, vm, `${hook} hook`); } } }}
errorCaptured与错误处理
它能够实现
- 将谬误发送给config.errorHandler
- 如果一个组件继承的链路或其父级隶属链路中存在多个errorCaptured钩子函数,则它们将会被雷同的谬误一一唤起。
- 一个errorCaptured钩子函数可能返回false来阻止谬误持续向上流传。它会阻止其余被这个谬误唤起的errorCaptured钩子函数和全局的config.errorHandler。
function handleError(err, vm, info) { if (vm) { let cur = vm; while ((cur = cur.$parent)) { const hooks = cur.$options.errorCaptured; if (hooks) { for (let i = 0; i < hooks.length; i++) { try { const capture = hooks[i].call(cur, err, vm, info) === false; if (capture) return; } catch (e) { globalHandleError(e, cur, "errorCaptured hook"); } } } } } globalHandleError(err, vm, info);}function globalHandleError(err, vm, info) { if (config.errorHandler) { try { return config.errorHandler.call(null, err, vm, info); } catch (e) { logError(e); } } logError(err);}function logError(err) { console.log(err);}
初始化实例属性
Vue.js通过initLifecycle函数向实例中挂载属性。
export function initLifecycle(vm) { const options = vm.$options; // 找出第一个非形象父类 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._isDestroyed = false; vm._isBeingDestroyed = false;}
初始化事件
初始化事件是指将父组件在模板中应用的v-on注册的事件增加到子组件的事件零碎(Vue.js的事件零碎)中。
这里的事件是父组件在模板中应用v-on监听子组件内触发的事件,不是浏览器事件。在初始化Vue.js实例时,有可能会接管父组件向子组件注册的事件。而子组件本身在模板中注册的事件,只有在渲染的时候才会依据虚构DOM的比照后果来确定是注册事件还是解绑事件。
Vue.js通过initEvents函数来执行初始化事件相干的逻辑
import { updateComponentListeners } from "./updateComponentListeners";export function initEvents(vm) { vm._events = Object.create(null); // 初始化父组件附加的事件 const listeners = vm.$options._parentListeners; if (listeners) { updateComponentListeners(vm, listeners); }}
updateComponentListeners
- 如果listeners对象中存在某个key(也就是事件名)在oldListeners中不存在,那么阐明这个事件是须要新增的事件;如果oldListeners中存在某些key在listeners中不存在,那么阐明这个事件是须要从事件零碎中移除的。
let target;function add(event, fn, once) { if (once) { target.$once(event, fn); } else { target.$on(event, fn); }}function remove(event, fn) { target.$off(event, fn);}export function updateComponentListeners(vm, listeners, oldListeners) { target = vm; updateListeners(listeners, oldListeners || {}, add, remove, vm);}function isUndef(i) { return i === null || i === undefined;}function normalizeEvent(name) { const passive = name.charAt(0) === "&"; name = passive ? name.slice(1) : name; const once = name.charAt(0) === "~"; name = once ? name.slice(1) : name; const capture = name.charAt(0) === "!"; name = capture ? name.slice(1) : name; return { name, once, capture, passive };}function updateListeners(on, oldOn, add, remove, vm) { let name, cur, old, event; for (name in on) { cur = on[name]; old = oldOn[name]; event = normalizeEvent(name); if (isUndef(cur)) { console.warn(""); } else if (isUndef(old)) { if (isUndef(cur.fns)) { cur = on[name] = createFnInvoker(cur); } add(event.name, cur, event.once, event.capture, event, passive); } else if (cur !== old) { old.fns = cur; on[name] = old; } } for (name in oldOn) { if (isUndef(on[name])) { event = normalizeEvent(name); remove(event.name, oldOn[name], event.capture); } }}
初始化inject
inject和provide选项须要一起应用,它们容许先人组件向其所有子孙后代注入依赖,并在其上下游关系成立的工夫里始终失效(无论组件档次有多深)。和react的上下文个性很类似。
inject在data/props之前初始化,而provide在data/props前面初始化。这样做的目标是让用户能够在data/props中应用inject所注入的内容。
初始化inject就是应用inject配置的key从以后组件读取内容,读取不到则读取它的父组件,以此类推。它是一个自底向上获取内容的过程,最终将找到的内容保留到实例(this)中,这样就能够间接在this上读取通过inject导入的注入内容。
export function initInjections(vm) { const result = resolveInject(vm.$options.inject, vm); if (result) { // 原理之前介绍过,不将内容转换为响应式。 observableState.shouldConvert = false; Object.keys(result).forEach(key => { defineReactive(vm, key, result[key]); }); observerState.shouldConvert = true; }}function resolveInject(inject, vm) { if (inject) { const result = Object.create(null); const keys = hasSymbol ? Reflect.ownKeys(inject).filter(key => { return Object.getOwnPropertyDescriptor(inject, key).enumerable; }) : Object.keys(inject); for (let i = 0; i < keys.length; i++) { const key = keys[i]; const provideKey = inject[key].from; let source = vm; while (source) { if (source._provided && provideKey in source._provided) { result[key] = source._provided[provideKey]; break; } source = source.$parent; } if (!source) { if ("default" in inject[key]) { const provideDefault = inject[key].default; result[key] = typeof provideDefault === "function" ? provideDefault.call(vm) : provideDefault; } else if (process.env.NODE_ENV !== "production") { // warn(`Injection "${key}" not found`, vm) } } } return result; }}
初始化状态
props、methods、data、computed、watch都是状态。
export function initState(vm) { 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 { observable((vm._data = {}), true /* asRootData */); } if (opts.computed) initComputed(vm, opts.computed); if (opts.watch && opts.watch !== nativeWatch) { initWatch(vm, opts.watch); }}
初始化props
props是父组件提供数据,子组件通过props字段抉择本人须要哪些内容,vue.js外部通过子组件的props选项将须要的数据筛选进去之后增加到子组件的上下文中。
1.子组件被实例化时,会先对props进行规格化解决,规格化之后的props为对象的格局。
function normalizeProps(options, vm) { const props = options.props; if (!props) return; const res = {}; let i, val, name; if (Array.isArray(props)) { i = props.length; while (i--) { val = props[i]; if (typeof val === "string") { name = camelize(val); res[name] = { type: null }; } else if (process.env.NODE_ENV !== "production") { console.warn("props must be strings when using array syntax"); } } } else if (isPlainObject(props)) { for (const key in props) { val = props[key]; name = camelize(key); res[name] = isPlainObject(val) ? val : { type: val }; } } else if (process.env.NODE_ENV !== "production") { console.warn(""); } options.props = res;}
2.初始化props
通过规格化之后的props从父组件传入的props数据中或从应用new创立实例时传入的propsData参数中,筛选出须要的数据保留在vm._props中,而后在vm上设置一个代理,实现通过vm.x拜访vm._props.x的目标。
function initProps(vm, propsOptions) { const propsData = vm.$options.propsData || {}; const props = (vm._props = {}); // 缓存props的key const keys = (vm.$options._propKeys = []); const isRoot = !vm.$parent; // root实例的props属性应该被转换成响应式数据 if (!isRoot) { toggleObserving(false); } for (const key in propsOptions) { keys.push(key); const value = validateProp(key, propsOptions, propsData, vm); defineReactive(props, key, value); if (!(key in vm)) { proxy(vm, `_props`, key); } } toggleObserving(true);}
validateProp能够失去prop key对应的value。
function validateProp(key, propsOptions, propsData, vm) { const prop = propOptions[key]; const absent = !hasOwn(propsData, key); let value = propsData[key]; // 解决布尔类型的props if (isType(Boolean, prop.type)) { if (absent && !hasOwn(prop, "default")) { value = false; } else if ( !isType(String, prop.type) && // hyphenate驼峰转换 (value === "" || value === hyphenate(key)) ) { value = true; } } // 查看默认值 if (value === undefined) { value = getPropDefaultValue(vm, prop, key); // 因为默认值是新的数据,所以须要将它转换成响应式的 const prevShouldConvert = observerState.shouldConvert; observerState.shouldConvert = true; observe(value); observerState.shouldConvert = prevShouldConvert; } if (process.env.NODE_ENV !== "production") { // 断言判断prop是否无效 assertProp(prop, key, value, vm, absent); } return value;}
assertProp的作用是当prop验证失败的时候,在非生产环境下,Vue.js将会产生一个控制台正告。
function assertProp(prop, name, value, vm, absent) { if (prop.required && absent) { console.warn('Missing required prop: "' + name + '"', vm); return; } if (value === null && !prop.required) { return; } let type = prop.type; let valid = !type || type === true; const expectedTypes = []; if (type) { if (!Array.isArray(type)) { type = [type]; } for (let i = 0; i < type.length && !valid; i++) { const assertedType = assertType(value, type[i]); expectedTypes.push(assertedType.expectedTypes || ""); valid = assertedType.valid; } } if (!valid) { console.warn(""); return; } const validator = prop.validator; if (validator) { if (!validator(value)) { console.warn(""); } }}
初始化methods
循环选项中的methods对象,并将每个属性顺次挂载到vm上即可。
function initMethods(vm, methods) { const props = vm.$options.props; for (const key in methods) { if (process.env.NODE_ENV !== "production") { if (methods[key] == null) { console.warn(""); } if (props && hasOwn(props, key)) { console.warn(""); } // isReserved判断字符串是否以$或_结尾 if (key in vm && isReserved(key)) { console.warn(""); } } vm[key] = methods[key] == null ? noop : bind(methods[key], vm); }}
初始化data
data中的数据最终会被保留到vm._data中,而后在vm上设置一个代理,使得通过vm.x能够拜访到vm._data中的属性。最初调用observe函数将data转换成响应式数据。
function initData(vm) { let data = vm.$options.data; data = vm._data = typeof data === "function" ? getData(data, vm) : data || {}; if (!isPlainObject(data)) { data = {}; process.env.NODE_ENV !== "production" && console.warn(""); } const keys = object.keys(data); const props = vm.$options.props; const methods = vm.$options.methods; let i = keys.length; while (i--) { const key = keys[i]; if (process.env.NODE_ENV !== "production") { if (methods && hasOwn(methods, key)) { console.warn(""); } } if (props && hasOwn(props, key)) { process.env.NODE_ENV !== "production" && console.warn(""); } else if (!isReserved(key)) { proxy(vm, `_data`, key); } } // 察看数据 observe(data, true /* asRootData */);}const sharedPropertyDefinition = { enumerable: true, configurable: true, get: noop, set: noop};function proxy(target, sourceKey, key) { sharedPropertyDefinition.get = function proxyGetter() { return this[sourceKey][key]; }; sharedPropertyDefinition.set = function proxySetter() { this[sourceKey][key] = val; }; Object.defineProperties(target, key, sharedPropertyDefinition);}
初始化computed
computed是定义在vm上的一个非凡的getter办法,get并不是用户提供的函数,而是vue.js外部的一个代理函数。在代理函数中能够联合Watcher实现缓存与收集依赖等性能。
当dirty属性为true时,阐明须要从新计算”计算属性“的返回值,当dirty属性为false时,阐明计算属性的值并没有变,不须要从新计算。
当计算属性中的内容发生变化后,计算属性的Watcher与组件的Watcher都会失去告诉。计算属性的Watcher会将本人的dirty属性设置为true,当下一次读取计算属性时,就会从新计算一次值。而后组件的Watcher也会收到告诉,从而执行render函数进行从新渲染的操作。因为要从新执行render函数,所以会从新计算读取计算属性的值,这时候计算属性的Watcher曾经把本人的dirty属性设置为true,所以会从新计算一次计算属性的值,用于本次渲染。
v2.5.17批改为判断最终计算属性的返回值是否变动。
const computedWatcherOptions = { lazy: true };function initComputed(vm, computed) { const watchers = (vm._computedWatchers = Object.create(null)); // 计算属性在SSR环境中,只是一个一般的getter办法 const isSSR = isServerRendering(); for (const key in computed) { const userDef = computed[key]; const getter = typeof userDef === "function" ? userDef : userDef.get; if (process.env.NODE_ENV !== "production" && getter === null) { console.warn(""); } // 在非SSR环境中,为计算属性创立外部观察器 if (!isSSR) { watchers[key] = new watchers( vm, getter || noop, noop, computedWatcherOptions ); } if (!(key in vm)) { defineComputed(vm, key, userDef); } else if (process.env.NODE_ENV !== "prodution") { if (key in vm.$data) { console.warn(); } else if (vm.$options.props && key in vm.$options.props) { console.warn(); } } }}const sharedPropertyDefinition = { enumerable: true, configurable: true, get: noop, set: noop};function defineComputed(target, key, userDef) { const shouldCache = !isServerRendering(); if (typeof userDef === "function") { sharedPropertyDefinition.get = shouldCache ? createComputedGetter(key) : userDef; sharedPropertyDefinition.set = noop; } else { sharedPropertyDefinition.get = userDef.get ? shouldCache && userDef.cache !== false ? createComputedGetter(key) : userDef.get : noop; sharedPropertyDefinition.set = userDef.set ? userDef.set : noop; } if ( process.env.NODE_ENV !== "production" && sharedPropertyDefinition.set === noop ) { sharedPropertyDefinition.set = function() { console.warn(); }; } Object.defineProperties(target, key, sharedPropertyDefinition);}// v.2.5.2function createComputedGetter(key) { return function computedGetter() { const watcher = this._computedWatchers && this._computedWatchers[key]; if (watcher) { if (watcher.dirty) { watcher.evaluate(); } if (Dep.target) { watcher.depend(); } return watcher.value; } };}// v2.5.17function createComputedGetter(key) { return function computedGetter() { const watcher = this._computedWatchers && this._computedWatchers[key]; if (watcher) { watcher.depend(); return watcher.evaluate(); } };}// Watcher专门定义了depend和evaluate办法用于实现计算属性相干的性能// v.2.5.2export default class Watcher { constructor(vm, expOrFn, cb, options) { // 暗藏无关代码 if (options) { this.lazy = !!options.lazy; } else { this.lazy = false; } this.dirty = this.lazy; this.value = this.lazy ? undefined : this.get(); } evaluate() { this.value = this.get(); this.dirty = false; } depend() { let i = this.deps.length; while (i--) { this.deps[i].depend(); } }}// v2.5.17class Watcher { constructor(vm, expOrFn, cb, options) { // 暗藏无关代码 if (options) { this.computed = !!options.computed; } else { this.computed = false; } this.dirty = this.computed; if (this.computed) { this.value = undefined; this.dep = new Dep(); } else { this.value = this.get(); } } update() { if (this.computed) { if (this.dep.subs.length === 0) { this.dirty = true; } else { this.getAndInvoke(() => { this.dep.notify(); }); } } // 暗藏无关代码 } getAndInvoke(cb) { const value = this.get(); if (value !== this.value || isObject(value) || this.deep) { const oldValue = this.value; this.value = value; this.dirty = false; if (this.user) { try { cb.call(this.vm, value, oldValue); } catch (e) { console.error(""); } } else { cb.call(this.vm, value, oldValue); } } } evaluate() { if (this.dirty) { this.value = this.get(); this.dirty = false; } return this.value; } depend() { if (this.dep && Dep.target) { this.dep.depend(); } }}
初始化watch
只须要循环watch选项,将对象中的每一项顺次调用vm.$watch办法来察看表达式即可。
function initWatch(vm, watch) { for (const key in watch) { const handler = watch[key]; if (Array.isArray(handler)) { for (let i = 0; i < handler.length; i++) { createWatcher(vm, key, handler[i]); } } else { createWatcher(vm, key, handler); } }}function createWatcher(vm, expOrFn, handler, options) { if (isPlainObject(handler)) { options = handler; handler = handler.handler; } if (typeof handler === "string") { handler = vm[handler]; } return vm.$watch(expOrFn, handler, options);}
初始化provide
赋值给vm._provided即可
function initProvide(vm) { const provide = vm.$options.provide; if (provide) { vm._provided = typeof provide === "function" ? provide.call(vm) : provide; }}
残缺代码 (https://github.com/mfaying/si...
参考
《深入浅出Vue.js》