Vue 生命周期


上图可以看出,Vue
实例的生命周期大致为四个节点:
- 初始化阶段:为
Vue
实例上初始化一些属性、事件以及响应式数据 - 模板编译节点:将模板编译成渲染函数
- 挂载阶段:将
Vue
实例挂载到指定的 DOM 节点上,即将模板渲染到真实DOM
- 销毁阶段: 将实例自身从父组件中删除,并取消依赖追踪及事件绑定,销毁子组件
new Vue 初始化阶段
仓库中代码路径:vue\src\core\instance\index.js
js
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;
initMixin(Vue)
:初始化阶段
js
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 */
if (process.env.NODE_ENV !== "production" && config.performance && mark) {
startTag = `vue-perf-start:${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(
/**
* 简单理解为返回 vm.constructor.options
* 相当于 Vue.options
*/
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(`vue ${vm._name} init`, startTag, endTag);
}
if (vm.$options.el) {
vm.$mount(vm.$options.el);
}
};
}
initGlobalApi(Vue)
:全局 API
js
export function initGlobalAPI(Vue: GlobalAPI) {
// config
const configDef = {};
configDef.get = () => config;
if (process.env.NODE_ENV !== "production") {
configDef.set = () => {
warn(
"Do not replace the Vue.config object, set individual fields instead."
);
};
}
Object.defineProperty(Vue, "config", configDef);
// exposed util methods.
// NOTE: these are not considered part of the public API - avoid relying on
// them unless you are aware of the risk.
Vue.util = {
warn,
extend,
mergeOptions,
defineReactive,
};
Vue.set = set;
Vue.delete = del;
Vue.nextTick = nextTick;
// 2.6 explicit observable API
Vue.observable = <T>(obj: T): T => {
observe(obj);
return obj;
};
{
/* // 创建一个空的 options */
}
Vue.options = Object.create(null);
ASSET_TYPES.forEach((type) => {
Vue.options[type + "s"] = Object.create(null);
});
{
/**
* Vue.component = {}
* Vue.directive = {}
* Vue.filter = {}
*/
}
{
/* // this is used to identify the "base" constructor to extend all plain-object
// components with in Weex's multi-instance scenarios. */
}
Vue.options._base = Vue;
extend(Vue.options.components, builtInComponents);
initUse(Vue);
initMixin(Vue);
initExtend(Vue);
initAssetRegisters(Vue);
}
js
export const ASSET_TYPES = ["component", "directive", "filter"];
function mergeHook(
parentVal: ?Array<Function>,
childVal: ?Function | ?Array<Function>
): ?Array<Function> {
const res = childVal
? parentVal
? parentVal.concat(childVal)
: Array.isArray(childVal)
? childVal
: [childVal]
: parentVal;
return res ? dedupeHooks(res) : res;
}
init lifecycle
给实例初始化生命周期,实例的属性初始化
js
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
js
<child @select="handleSelect" @click.native="handleClick"></child>
// 拿到两种事件
el.events = {
select:{
value:'handleSelect'
}
}
el.nativeEvents = {
click:{
value:'handleClick'
}
}
// event handlers
if (el.events) {
data += `${genHandlers(el.events, false)},`
}
if (el.nativeEvents) {
data += `${genHandlers(el.nativeEvents, true)},`
}
export function genHandlers (
events: ASTElementHandlers,
isNative: boolean
): string {
const prefix = isNative ? 'nativeOn:' : 'on:'
let staticHandlers = ``
let dynamicHandlers = ``
for (const name in events) {
const handlerCode = genHandler(events[name])
if (events[name] && events[name].dynamic) {
dynamicHandlers += `${name},${handlerCode},`
} else {
staticHandlers += `"${name}":${handlerCode},`
}
}
staticHandlers = `{${staticHandlers.slice(0, -1)}}`
if (dynamicHandlers) {
return prefix + `_d(${staticHandlers},[${dynamicHandlers.slice(0, -1)}])`
} else {
return prefix + staticHandlers
}
}
// 得出下面的参数
{
on:{
'select': handleSelect
},
nativeOn:{
'click': function($event) {
return handleClick($event)
}
}
}
// 模板初始化的时候,给实例初始化事件
// extract listeners, since these needs to be treated as
// child component listeners instead of DOM listeners
const listeners = data.on
// replace with listeners with .native modifier
// so it gets processed during parent component patch.
data.on = data.nativeOn
结论 父组件给子组件注册事件中,把自定义事件传给子组件,在子组件实例化的时候进行初始化,而浏览器原生事件是在父组件中处理的 实例初始化阶段,调用 initEvents
方法,实际上初始化的是父组件在模板中使用v-on
指令或@
注册的监听子组件内触发的事件
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);
}
}
initInjections
允许一个组件向子孙后代组件注入一个依赖,不论组件层级有多深,始终都生效。子孙后代组件都可以通过 this.$injector
获取到依赖
inject
选项应该是:一个字符串数组或对象
js
// 父组件
var parent = {
provide: {
foo: "foo",
},
};
// 子组件
var child = {
inject: ["foo"],
create() {
console.log(this.foo); // 'foo'
},
};
- inject 传递的是一个对象,可以使用
Symbol
js
const s = Symbol();
const Provider = {
provide() {
return {
[s]: 123,
};
},
// 使用函数返回值作为依赖
};
const child = {
inject: { s },
create() {
console.log(this[s]); // 123
},
};
- 注意:
provide
和inject
选项绑定的数据,只能在create
生命周期中使用,不能在beforeCreate
生命周期中使用,provide
和inject
选项绑定的数据不是响应式的。
仓库源码路径:vue\src\core\instance\inject.js
js
export function initInjections(vm: Component) {
const result = resolveInject(vm.$options.inject, vm);
if (result) {
toggleObserving(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]);
}
});
toggleObserving(true);
}
}
export function resolveInject(inject: any, vm: Component): ?Object {
if (inject) {
// inject is :any because flow is not smart enough to figure out cached
const result = Object.create(null);
const keys = hasSymbol ? Reflect.ownKeys(inject) : Object.keys(inject);
for (let i = 0; i < keys.length; i++) {
const key = keys[i];
// #6574 in case the inject object is observed...
if (key === "__ob__") continue;
const provideKey = inject[key].from;
let source = vm;
while (source) {
if (source._provided && hasOwn(source._provided, provideKey)) {
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;
}
}
export function toggleObserving(value: boolean) {
shouldObserve = value;
// 所以 provide 和 inject 的数据不是响应式的
}
js
function normalizeInject(options: Object, vm: ?Component) {
const inject = options.inject;
if (!inject) return;
const normalized = (options.inject = {});
if (Array.isArray(inject)) {
for (let i = 0; i < inject.length; i++) {
normalized[inject[i]] = { from: inject[i] };
}
} else if (isPlainObject(inject)) {
for (const key in inject) {
const val = inject[key];
normalized[key] = isPlainObject(val)
? extend({ from: key }, val)
: { from: val };
}
} else if (process.env.NODE_ENV !== "production") {
warn(
`Invalid value for option "inject": expected an Array or an Object, ` +
`but got ${toRawType(inject)}.`,
vm
);
}
}
initState
初始化状态 源码路径:vue\src\core\instance\state.js
js
export function initState(vm: Component) {
vm._watchers = []; /* 存储组件中的 watcher */
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);
}
}
js
props: ["name"];
props:{
name: String
}
props:{
name: {
type: String,
default: "0",
required: true
}
}
normalizeProps
源码路径:vue\src\core\util\options.js
js
function normalizeProps(options: Object, vm: ?Component) {
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") {
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") {
warn(
`Invalid value for option "props": expected an Array or an Object, ` +
`but got ${toRawType(props)}.`,
vm
);
}
options.props = res;
}
- 将
props
数据规范化处理后,交给initProps
处理。路径:vue\src\core\instance\state.js
-
初始化
props
js// 初始化 props function initProps(vm: Component, propsOptions: Object) { const propsData = vm.$options.propsData || {}; const props = (vm._props = {}); // cache prop keys so that future props updates can iterate using Array // instead of dynamic object key enumeration. // 这里的 keys 就是 props 的 key,用来缓存 props 对象中的 key,将来的 props 更新可以的时候,只需要遍历 vm.$options._propKeys 数组即可得到所有 props 的 key const keys = (vm.$options._propKeys = []); const isRoot = !vm.$parent; // root instance props should be converted if (!isRoot) { toggleObserving(false); } for (const key in propsOptions) { keys.push(key); const value = validateProp(key, propsOptions, propsData, vm); // 校验 props. validateProp 校验函数 /* istanbul ignore else */ if (process.env.NODE_ENV !== "production") { const hyphenatedKey = hyphenate(key); if ( isReservedAttribute(hyphenatedKey) || config.isReservedAttr(hyphenatedKey) ) { warn( `"${hyphenatedKey}" is a reserved attribute and cannot be used as component prop.`, vm ); } defineReactive(props, key, value, () => { if (!isRoot && !isUpdatingChildComponent) { warn( `Avoid mutating a prop directly since the value will be ` + `overwritten whenever the parent component re-renders. ` + `Instead, use a data or computed property based on the prop's ` + `value. Prop being mutated: "${key}"`, vm ); } }); } else { defineReactive(props, key, value); } // static props are already proxied on the component's prototype // during Vue.extend(). We only need to proxy props defined at // instantiation here. if (!(key in vm)) { proxy(vm, `_props`, key); } } toggleObserving(true); }
-
校验
props
js// 校验 props export function validateProp( key: string, propOptions: Object, propsData: Object, vm?: Component ): any { const prop = propOptions[key]; const absent = !hasOwn(propsData, key); let value = propsData[key]; // boolean casting const booleanIndex = getTypeIndex(Boolean, prop.type); if (booleanIndex > -1) { // 父组件没有传入该 prop 属性,并且该属性没有设置默认值 if (absent && !hasOwn(prop, "default")) { value = false; } else if (value === "" || value === hyphenate(key)) { // only cast empty string / same name to boolean if // boolean has higher priority const stringIndex = getTypeIndex(String, prop.type); if (stringIndex < 0 || booleanIndex < stringIndex) { value = true; } } } // check default value if (value === undefined) { value = getPropDefaultValue(vm, prop, key); // since the default value is a fresh copy, // make sure to observe it. const prevShouldObserve = shouldObserve; toggleObserving(true); observe(value); toggleObserving(prevShouldObserve); } if ( process.env.NODE_ENV !== "production" && // skip validation for weex recycle-list child component props !(__WEEX__ && isObject(value) && "@binding" in value) ) { assertProp(prop, key, value, vm, absent); } return value; }
-
设置
props
初始值:getPropDefaultValue
js/** * Get the default value of a prop. */ function getPropDefaultValue( vm: ?Component, prop: PropOptions, key: string ): any { // no default, return undefined if (!hasOwn(prop, "default")) { return undefined; } const def = prop.default; // warn against non-factory defaults for Object & Array if (process.env.NODE_ENV !== "production" && isObject(def)) { warn( 'Invalid default value for prop "' + key + '": ' + "Props with type Object/Array must use a factory function " + "to return the default value.", vm ); } // the raw prop value was also undefined from previous render, // return previous default value to avoid unnecessary watcher trigger if ( vm && vm.$options.propsData && vm.$options.propsData[key] === undefined && vm._props[key] !== undefined ) { return vm._props[key]; } // call factory function for non-Function types // a value is Function if its prototype is function even across different execution context return typeof def === "function" && getType(prop.type) !== "Function" ? def.call(vm) : def; } /** * Assert whether a prop is valid. * 断言 判断 prop 是否合法 */ function assertProp( prop: PropOptions, name: string, value: any, vm: ?Component, // 判断当前 key 是否在 propsData 中存在,也就是是否在父组件中传入 absent: boolean ) { if (prop.required && absent) { warn('Missing required prop: "' + name + '"', vm); return; } if (value == null && !prop.required) { return; } let type = prop.type; let valid = !type || type === true; /** * props:{name: true} 对应 value === true */ const expectedTypes = []; // 期望接收的类型 if (type) { if (!Array.isArray(type)) { type = [type]; } for (let i = 0; i < type.length && !valid; i++) { /** * valid:true 表示校验是否成功 * expectedType: Boolean,String 等,期望接收的被校验的类型 */ const assertedType = assertType(value, type[i]); expectedTypes.push(assertedType.expectedType || ""); valid = assertedType.valid; } } if (!valid) { warn(getInvalidTypeMessage(name, value, expectedTypes), vm); return; } const validator = prop.validator; if (validator) { if (!validator(value)) { warn( 'Invalid prop: custom validator check failed for prop "' + name + '".', vm ); } } }
initMethods
js
function initMethods(vm: Component, methods: Object) {
const props = vm.$options.props;
for (const key in methods) {
if (process.env.NODE_ENV !== "production") {
// 先判断 methods[key] 是否是函数
if (typeof methods[key] !== "function") {
warn(
`Method "${key}" has type "${typeof methods[
key
]}" in the component definition. ` +
`Did you reference the function correctly?`,
vm
);
}
if (props && hasOwn(props, key)) {
warn(`Method "${key}" has already been defined as a prop.`, vm);
}
if (key in vm && isReserved(key)) {
// isReserved 判断函数名是否以 _ 或者 $ 开头
warn(
`Method "${key}" conflicts with an existing Vue instance method. ` +
`Avoid defining component methods that start with _ or $.`
);
}
}
// 判断没问题,就把方法绑定到 vm 实例
vm[key] =
typeof methods[key] !== "function" ? noop : bind(methods[key], vm);
}
}
function isReserved(str) {
var c = (str + "").charCodeAt(0);
return c === 0x24 || c === 0x5f;
}
initData
js
function initData(vm: Component) {
let data = vm.$options.data;
data = vm._data = typeof data === "function" ? getData(data, vm) : data || {};
if (!isPlainObject(data)) {
data = {};
process.env.NODE_ENV !== "production" &&
warn(
"data functions should return an object:\n" +
"https://vuejs.org/v2/guide/components.html#data-Must-Be-a-Function",
vm
);
}
// proxy data on instance
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)) {
warn(
`Method "${key}" has already been defined as a data property.`,
vm
);
}
}
if (props && hasOwn(props, key)) {
process.env.NODE_ENV !== "production" &&
warn(
`The data property "${key}" is already declared as a prop. ` +
`Use prop default value instead.`,
vm
);
} else if (!isReserved(key)) {
// 通过 proxy 代理到 vm 上,我们就可以通过 this.xxx 访问 data 里面的数据
proxy(vm, `_data`, key);
}
}
// observe data
observe(data, true /* asRootData */);
}
initComputed
-
computed
jsconst vm = new Vue({ data: { a: 1 }, computed: { aPlus: { get: function () { return this.a + 1; }, set: function (newValue) { this.a = newValue - 1; }, }, aDouble: function () { return this.a * 2; }, }, });
-
initComputed
jsfunction initComputed(vm: Component, computed: Object) { // $flow-disable-line const watchers = (vm._computedWatchers = Object.create(null)); // computed properties are just getters during SSR 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) { warn(`Getter is missing for computed property "${key}".`, vm); } if (!isSSR) { // create internal watcher for the computed property. watchers[key] = new Watcher( vm, getter || noop, noop, computedWatcherOptions ); } // component-defined computed properties are already defined on the // component prototype. We only need to define computed properties defined // at instantiation here. if (!(key in vm)) { defineComputed(vm, key, userDef); } else if (process.env.NODE_ENV !== "production") { if (key in vm.$data) { warn( `The computed property "${key}" is already defined in data.`, vm ); } else if (vm.$options.props && key in vm.$options.props) { warn( `The computed property "${key}" is already defined as a prop.`, vm ); } } } }
-
defineComputed
js// 给 target 定义一个属性 key,并且属性 key 的 getter 和 setter 根据 userDef 的值来定义 export function defineComputed( target: any, key: string, userDef: Object | Function ) { const shouldCache = !isServerRendering(); if (typeof userDef === "function") { sharedPropertyDefinition.get = shouldCache ? // 需要创建一个具有缓存功能的 computed getter createComputedGetter(key) : // 服务端环境下计算属性不需要缓存 createGetterInvoker(userDef); sharedPropertyDefinition.set = noop; } else { sharedPropertyDefinition.get = userDef.get ? shouldCache && userDef.cache !== false ? createComputedGetter(key) : createGetterInvoker(userDef.get) : noop; sharedPropertyDefinition.set = userDef.set || noop; } if ( process.env.NODE_ENV !== "production" && sharedPropertyDefinition.set === noop ) { sharedPropertyDefinition.set = function () { warn( `Computed property "${key}" was assigned to but it has no setter.`, this ); }; } Object.defineProperty(target, key, sharedPropertyDefinition); }
-
createComputedGetter: 创建一个具有缓存功能的 computed getter
js
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;
}
};
}
// watcher 源码路径:vue\src\core\observer\watcher.js
class watcher {
constructor(
vm: Component,
expOrFn: string | Function,
cb: Function,
options?: ?Object,
isRenderWatcher?: boolean
) {
// ....
}
/**
* Evaluate the value of the watcher.
* This only gets called for lazy watchers.
*/
evaluate() {
this.value = this.get();
this.dirty = false;
}
/**
* Depend on all deps collected by this watcher.
*/
depend() {
let i = this.deps.length;
while (i--) {
this.deps[i].depend();
}
}
/**
* Subscriber interface.
* Will be called when a dependency changes.
*/
update() {
/* istanbul ignore else */
if (this.lazy) {
this.dirty = true;
} else if (this.sync) {
this.run();
} else {
queueWatcher(this);
}
}
}
new Watcher
的时候,传入
js
const computedWatcherOptions = {
lazy: true,
};
initWatch
- watch
js
const vm = new Vue({
data: {
a: 1,
b: 2,
c: 3,
d: 4,
},
watch: {
a: function (val, oldVal) {
console.log(val, oldVal);
},
b: "someMethod",
c: {
handler: function (val, oldVal) {
console.log(val, oldVal);
},
deep: true,
},
d: {
handler: "someMethod",
immediate: true,
},
e: [
"handler",
"someMethod",
function handler2(val, oldVal) {
console.log(val, oldVal);
},
],
"f.g": function (val, oldVal) {
console.log(val, oldVal);
},
},
methods: {
someMethod: function (val, oldVal) {
console.log(val, oldVal);
},
},
});
- createWatcher
js
function createWatcher(
vm: Component,
expOrFn: string | Function,
handler: any,
options?: Object
) {
if (isPlainObject(handler)) {
options = handler;
handler = handler.handler;
}
// 在 initMethods 的时候,已经将选项助攻的每一个方法都绑定到当前实例上了
if (typeof handler === "string") {
handler = vm[handler];
}
return vm.$watch(expOrFn, handler, options);
}
挂载阶段

js
Vue.prototype.$mount = function (
el?: string | Element,
hydrating?: boolean
): Component {
el = el && inBrowser ? query(el) : undefined;
return mountComponent(this, el, hydrating);
};
mountComponent
源码路径:vue\src\core\instance\lifecycle.js
js
updateComponent = () => {
const name = vm._name;
const id = vm._uid;
const startTag = `vue-perf-start:${id}`;
const endTag = `vue-perf-end:${id}`;
mark(startTag);
const vnode = vm._render();
mark(endTag);
measure(`vue ${name} render`, startTag, endTag);
mark(startTag);
vm._update(vnode, hydrating);
mark(endTag);
measure(`vue ${name} patch`, startTag, endTag);
};
// updateComponent 中读取到的所有数据,都会被 watcher 监控,这些数据只要有一个发生变化,那么 watchers 就会得到通知,从而去更新视图
new Watcher(
vm,
updateComponent,
noop,
{
before() {
if (vm._isMounted && !vm._isDestroyed) {
callHook(vm, "beforeUpdate");
}
},
},
true /* isRenderWatcher */
);
销毁阶段

js
Vue.prototype.$destroy = function () {
const vm: Component = this;
if (vm._isBeingDestroyed) {
// 标志当前实例是否处于被销毁阶段的状态
return;
}
callHook(vm, "beforeDestroy");
vm._isBeingDestroyed = true;
// remove self from parent
const parent = vm.$parent;
if (parent && !parent._isBeingDestroyed && !vm.$options.abstract) {
remove(parent.$children, vm); // 把自己从父级实例的子实例列表中删除
}
// teardown watchers
// 删除两部分:一部分是实例自身依赖其他数据,需要将实例自身从其他数据的依赖列表中删除
// 另一部分是实例内的数据对其他数据的依赖,也需要从其他数据的依赖列表中删除
if (vm._watcher) {
vm._watcher.teardown(); // 从所有依赖向的 Dep 列表中将自己删除
}
let i = vm._watchers.length;
while (i--) {
vm._watchers[i].teardown(); // 从所有依赖向的 Dep 列表中将自己删除
}
// remove reference from data ob
// frozen object may not have observer.
if (vm._data.__ob__) {
vm._data.__ob__.vmCount--;
}
// call the last hook...
vm._isDestroyed = true;
// invoke destroy hooks on current rendered tree
vm.__patch__(vm._vnode, null);
// fire destroyed hook
callHook(vm, "destroyed");
// turn off all instance listeners.
vm.$off(); // 移除实例的所有事件监听
// remove __vue__ reference
if (vm.$el) {
vm.$el.__vue__ = null;
}
// release circular reference (#6759)
if (vm.$vnode) {
vm.$vnode.parent = null;
}
};
实例方法
数据相关的实例方法
源码路径:vue\src\core\instance\state.js
与数据相关的实例方法有 3 个,分别是vm.$set
,vm.$delete
,vm.$watch
. 是在stateMixin
函数中挂载到Vue
原型的
$set
,$delete
源码路径:vue\src\core\observer\index.js
js
export function stateMixin(Vue: Class<Component>) {
// flow somehow has problems with directly declared definition object
// when using Object.defineProperty, so we have to procedurally build up
// the object here.
const dataDef = {};
dataDef.get = function () {
return this._data;
};
const propsDef = {};
propsDef.get = function () {
return this._props;
};
if (process.env.NODE_ENV !== "production") {
dataDef.set = function () {
warn(
"Avoid replacing instance root $data. " +
"Use nested data properties instead.",
this
);
};
propsDef.set = function () {
warn(`$props is readonly.`, this);
};
}
Object.defineProperty(Vue.prototype, "$data", dataDef);
Object.defineProperty(Vue.prototype, "$props", propsDef);
Vue.prototype.$set = set;
Vue.prototype.$delete = del;
Vue.prototype.$watch = function (
expOrFn: string | Function,
cb: any,
options?: Object
): Function {
const vm: Component = this;
if (isPlainObject(cb)) {
return createWatcher(vm, expOrFn, cb, options);
}
options = options || {};
options.user = true;
const watcher = new Watcher(vm, expOrFn, cb, options);
if (options.immediate) {
try {
cb.call(vm, watcher.value);
} catch (error) {
handleError(
error,
vm,
`callback for immediate watcher "${watcher.expression}"`
);
}
}
return function unwatchFn() {
watcher.teardown();
};
};
}
事件相关的实例方法
源码路径:vue\src\core\instance\events.js
与事件相关的实例方法有 4 个,分别是vm.$on
,vm.$emit
,vm.$off
,vm.$once
. 在eventsMixin
函数中挂载到Vue
原型上
生命周期相关的方法
与生命周期相关的实例方法有 4 个,分别是vm.$mount
,vm.$forceUpdate
,vm.$nextTick
,vm.$destroy
vm.$nextTick
源码路径:vue\src\core\util\next-tick.js
vm.$destroy
,vm.$forceUpdate
源码路径:vue\src\core\instance\lifecycle.js
------lifecycleMixin
vm.$mount
源码路径:vue\src\core\instance\lifecycle.js
------mountComponent
全局 API
与实例方法不同,实例方法是将方法挂载到Vue
的原型上,而全局 API 是直接在Vue
上挂载的方法。在 Vue 中,全局 API 一共有 12 个,分别是 Vue.extend、Vue.nextTick、Vue.set、Vue.delete、Vue.directive、Vue.filter、Vue.component、Vue.use、Vue.mixin、Vue.observable、Vue.version
。这 12 个 API 中有的是我们在日常业务开发中经常会用到的,有的是对 Vue 内部或外部插件提供的,我们在日常业务开发中几乎用不到。
过滤器
过滤器有两种使用方式,分别是在双花括号插值中和在 v-bind 表达式中。但是无论是哪一种使用方式,过滤器都是写在模板里面的。既然是写在模板里面,那么它就会被编译,会被编译成渲染函数字符串,然后在挂载的时候会执行渲染函数,从而就会使过滤器生效。
vm.$filters
源码路径:vue\src\compiler\parser\filter-parser.js
js
// {{ message | name}}
<div v-bind:id="userId | adminId"></div>;
filter: {
// ......
}
Vue.filter("userId", () => {});
指令
在 Vue 中,除了 Vue 本身为我们提供的一些内置指令之外,Vue 还支持用户自定义指令。并且用户有两种定义指令的方式:一种是使用全局 API------Vue.directive 来定义全局指令,这种方式定义的指令会被存放在 Vue.options['directives']中;另一种是在组件内的 directive 选项中定义专为该组件使用的局部指令,这种方式定义的指令会被存放在 vm.$options['directives']中。
js
function updateDirectives(oldVnode: VNodeWithData, vnode: VNodeWithData) {
if (oldVnode.data.directives || vnode.data.directives) {
_update(oldVnode, vnode);
}
}
- 可以看到,无论是使用哪一种方式定义的指令它都是将定义好的指令存放在指定的地方,而并不能让指令生效。那么定义的指令什么时候才会生效呢?或者说它是如何生效的呢?
- 源码路径:
src/core/vdom/moudles/directives.js
js
function _update(oldVnode, vnode) {
const isCreate = oldVnode === emptyNode;
const isDestroy = vnode === emptyNode;
const oldDirs = normalizeDirectives(
oldVnode.data.directives,
oldVnode.context
);
const newDirs = normalizeDirectives(vnode.data.directives, vnode.context);
const dirsWithInsert = [];
const dirsWithPostpatch = [];
let key, oldDir, dir;
for (key in newDirs) {
oldDir = oldDirs[key];
dir = newDirs[key];
if (!oldDir) {
// new directive, bind
callHook(dir, "bind", vnode, oldVnode);
if (dir.def && dir.def.inserted) {
dirsWithInsert.push(dir);
}
} else {
// existing directive, update
dir.oldValue = oldDir.value;
dir.oldArg = oldDir.arg;
callHook(dir, "update", vnode, oldVnode);
if (dir.def && dir.def.componentUpdated) {
dirsWithPostpatch.push(dir);
}
}
}
if (dirsWithInsert.length) {
// 当一个新建的元素节点被插入到父节点时,虚拟DOM渲染更新的 insert 钩子函数和指令的 inserted 钩子函数都要被触发
const callInsert = () => {
for (let i = 0; i < dirsWithInsert.length; i++) {
callHook(dirsWithInsert[i], "inserted", vnode, oldVnode);
}
};
if (isCreate) {
mergeVNodeHook(vnode, "insert", callInsert);
} else {
callInsert();
}
}
if (dirsWithPostpatch.length) {
mergeVNodeHook(vnode, "postpatch", () => {
for (let i = 0; i < dirsWithPostpatch.length; i++) {
callHook(dirsWithPostpatch[i], "componentUpdated", vnode, oldVnode);
}
});
}
if (!isCreate) {
for (key in oldDirs) {
if (!newDirs[key]) {
// no longer present, unbind
callHook(oldDirs[key], "unbind", oldVnode, oldVnode, isDestroy);
}
}
}
}
/**
* @description: 处理指令
*/
function normalizeDirectives(
dirs: ?Array<VNodeDirective>,
vm: Component
): { [key: string]: VNodeDirective } {
const res = Object.create(null);
if (!dirs) {
// $flow-disable-line
return res;
}
let i, dir;
for (i = 0; i < dirs.length; i++) {
dir = dirs[i];
if (!dir.modifiers) {
// $flow-disable-line
dir.modifiers = emptyModifiers;
}
res[getRawDirName(dir)] = dir;
dir.def = resolveAsset(vm.$options, "directives", dir.name, true);
}
// $flow-disable-line
return res;
}
// 自定义化指令
{
'v-focus': {
name: 'focus'//
value: '',
arg: '',
modifiers: {} //修饰符
def: {
inserted: fn
}
}
}
- 所谓让指令生效,就是要在合适的时机执行定义指令时所设置的钩子函数
内置组件
<keep-alive>
是 Vue 实现的一个内置组件,也就是说 Vue 源码不仅实现了一套组件化的机制,也实现了一些内置组件,关于<keep-alive>
组件,官网如下介绍:<keep-alive>
是 Vue 中内置的一个抽象组件,它自身不会渲染一个 DOM 元素,也不会出现在父组件链中。当它包裹动态组件时,会缓存不活动的组件实例,而不是销毁它们。- 简单来说:就是我们可以把一些不常变动的组件或者需要缓存的组件用
<keep-alive>
包裹起来,这样<keep-alive>
就会帮我们把组件保存在内存中,而不是直接的销毁,这样做可以保留组件的状态或避免多次重新渲染,以提高页面性能。
js
<div>
<keep-alive include="aa">
<my-input></my-input>
</keep-alive>
</div>
- 源码路径:
vue\src\core\components\keep-alive.js
js
/* @flow */
import { isRegExp, remove } from "shared/util";
import { getFirstComponentChild } from "core/vdom/helpers/index";
type VNodeCache = { [key: string]: ?VNode };
function getComponentName(opts: ?VNodeComponentOptions): ?string {
return opts && (opts.Ctor.options.name || opts.tag);
}
function matches(
pattern: string | RegExp | Array<string>,
name: string
): boolean {
if (Array.isArray(pattern)) {
return pattern.indexOf(name) > -1;
} else if (typeof pattern === "string") {
return pattern.split(",").indexOf(name) > -1;
} else if (isRegExp(pattern)) {
return pattern.test(name);
}
/* istanbul ignore next */
return false;
}
function pruneCache(keepAliveInstance: any, filter: Function) {
const { cache, keys, _vnode } = keepAliveInstance;
for (const key in cache) {
const cachedNode: ?VNode = cache[key];
if (cachedNode) {
const name: ?string = getComponentName(cachedNode.componentOptions);
if (name && !filter(name)) {
pruneCacheEntry(cache, key, keys, _vnode);
}
}
}
}
function pruneCacheEntry(
cache: VNodeCache,
key: string,
keys: Array<string>,
current?: VNode
) {
const cached = cache[key];
if (cached && (!current || cached.tag !== current.tag)) {
cached.componentInstance.$destroy();
}
cache[key] = null;
remove(keys, key);
}
const patternTypes: Array<Function> = [String, RegExp, Array];
export default {
name: "keep-alive",
abstract: true,
props: {
include: patternTypes,
exclude: patternTypes,
max: [String, Number],
},
created() {
this.cache = Object.create(null);
this.keys = [];
},
destroyed() {
for (const key in this.cache) {
pruneCacheEntry(this.cache, key, this.keys);
}
},
mounted() {
this.$watch("include", (val) => {
pruneCache(this, (name) => matches(val, name));
});
this.$watch("exclude", (val) => {
pruneCache(this, (name) => !matches(val, name));
});
},
render() {
const slot = this.$slots.default;
const vnode: VNode = getFirstComponentChild(slot);
const componentOptions: ?VNodeComponentOptions =
vnode && vnode.componentOptions;
if (componentOptions) {
// check pattern
const name: ?string = getComponentName(componentOptions);
const { include, exclude } = this;
if (
// not included
(include && (!name || !matches(include, name))) ||
// excluded
(exclude && name && matches(exclude, name))
) {
return vnode;
}
const { cache, keys } = this;
const key: ?string =
vnode.key == null
? // same constructor may get registered as different local components
// so cid alone is not enough (#3269)
componentOptions.Ctor.cid +
(componentOptions.tag ? `::${componentOptions.tag}` : "")
: vnode.key;
if (cache[key]) {
vnode.componentInstance = cache[key].componentInstance;
// make current key freshest
remove(keys, key);
keys.push(key);
} else {
cache[key] = vnode;
keys.push(key);
// prune oldest entry
if (this.max && keys.length > parseInt(this.max)) {
pruneCacheEntry(cache, keys[0], keys, this._vnode);
}
}
vnode.data.keepAlive = true;
}
return vnode || (slot && slot[0]);
},
};
keep-alive
组件使用了LRU
的缓存策略