残缺代码 (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.2
function 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.17
function 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.2
export 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.17
class 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》
发表回复