Vue源码解析(叁)

Vue 生命周期

上图可以看出,Vue 实例的生命周期大致为四个节点:

  1. 初始化阶段:为Vue实例上初始化一些属性、事件以及响应式数据
  2. 模板编译节点:将模板编译成渲染函数
  3. 挂载阶段:将Vue实例挂载到指定的 DOM 节点上,即将模板渲染到真实DOM
  4. 销毁阶段: 将实例自身从父组件中删除,并取消依赖追踪及事件绑定,销毁子组件

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
	},
};
  • 注意:
    • provideinject选项绑定的数据,只能在create生命周期中使用,不能在beforeCreate生命周期中使用,
    • provideinject选项绑定的数据不是响应式的

仓库源码路径: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
  1. 初始化 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);
    }
  2. 校验 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;
    }
  3. 设置 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

    js 复制代码
    const 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

    js 复制代码
    function 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.$destroyvm.$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 的缓存策略
相关推荐
欢乐小v8 分钟前
elementui-admin构建
前端·javascript·elementui
霸道流氓气质35 分钟前
Vue中使用vue-3d-model实现加载3D模型预览展示
前端·javascript·vue.js
溜达溜达就好43 分钟前
ubuntu22 npm install electron --save-dev 失败
前端·electron·npm
慧一居士1 小时前
Axios 完整功能介绍和完整示例演示
前端
晨岳1 小时前
web开发-CSS/JS
前端·javascript·css
22:30Plane-Moon1 小时前
前端之CSS
前端·css
半生过往1 小时前
前端上传 pdf 文件 ,前端自己解析出来 生成界面 然后支持编辑
前端·pdf
晨岳1 小时前
web开发基础(CSS)
前端·css
.又是新的一天.1 小时前
前端-CSS (样式引入、选择器)
前端·css