/** * 将props上的属性代理到vm上 */ for (const key in propsOptions) { // 将key存入vm.$options._propKeys keys.push(key) // 验证prop const value = validateProp(key, propsOptions, propsData, vm)
/** * 改造getter和setter,纳入观察 */ 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, ) } // 在setter中提示不要在子组件中直接修改props的属性,而是让父组件传入 defineReactive(props, key, value, () => { if (vm.$parent && !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) }
// Vue.extend()期间,静态props已经被代理到组件原型上,因此只需要代理props if (!(key in vm)) { proxy(vm, `_props`, key) } } toggleObserving(true) }
初始化methods
methods 中定义的方法不需要做响应式处理,因此也不需要做代理,直接验证没有重名的情况下,挂载到 vm 上,并 bind 它的 this 指针为 vm。
functioninitMethods(vm: Component, methods: Object) { const props = vm.$options.props for (const key in methods) { if (process.env.NODE_ENV !== 'production') { // 保证不是null或者undefined if (methods[key] == null) { warn( `Method "${key}" has an undefined value in the component definition. ` + `Did you reference the function correctly?`, vm, ) } // 保证不和props的属性重名 if (props && hasOwn(props, key)) { warn( `Method "${key}" has already been defined as a prop.`, vm, ) } // 保证不是vm保留字段 if ((key in vm) && isReserved(key)) { warn( `Method "${key}" conflicts with an existing Vue instance method. ` + `Avoid defining component methods that start with _ or $.`, ) } } // 方法可以直接挂载,无需proxy,注意bind它的this指针到vm实例 vm[key] = methods[key] == null ? noop : bind(methods[key], vm) } }
// 避免重复定义同名的属性 if (!(key in vm)) { defineComputed(vm, key, userDef) } elseif (process.env.NODE_ENV !== 'production') { if (key in vm.$data) { warn(`The computed property "${key}" is already defined in data.`, vm) } elseif (vm.$options.props && key in vm.$options.props) { warn(`The computed property "${key}" is already defined as a prop.`, vm) } } } }
// 挂载组件 exportfunctionmountComponent(vm: Component, el: ?Element, hydrating?: boolean): Component{ vm.$el = el if (!vm.$options.render) { // render函数不存在的时候创建一个空的VNode节点 vm.$options.render = createEmptyVNode
if (process.env.NODE_ENV !== 'production') { if ((vm.$options.template && vm.$options.template.charAt(0) !== '#') || vm.$options.el || el) { warn( 'You are using the runtime-only build of Vue where the template ' + 'compiler is not available. Either pre-compile the templates into ' + 'render functions, or use the compiler-included build.', vm, ) } else { warn( 'Failed to mount component: template or render function not defined.', vm, ) } } }
// 触发beforeMount钩子 callHook(vm, 'beforeMount')
// updateComponent作为Watcher对象的getter函数,用来依赖收集 let updateComponent
// 判断组件是否有slot子组件,需要在覆盖$options._renderChildren完成 const hasChildren = !!( renderChildren || // has new static slots vm.$options._renderChildren || // has old static slots parentVnode.data.scopedSlots || // has new scoped slots vm.$scopedSlots !== emptyObject // has old scoped slots )
// Vue.$off实现,移除某个事件回调 Vue.prototype.$off = function (event?: string | Array<string>, fn?: Function): Component{ const vm: Component = this
// 不传参数,移除所有监听器 if (!arguments.length) { // 把_events字典整个替换 vm._events = Object.create(null) return vm }
// 传入数组,遍历拆分后逐个递归处理 if (Array.isArray(event)) { for (let i = 0, l = event.length; i < l; i++) { this.$off(event[i], fn) } return vm }
// 对于明确的单个事件移除 const cbs = vm._events[event] if (!cbs) { return vm } // 没有传入具体的handler,则删除该event名称下所有回调 if (!fn) { vm._events[event] = null return vm } // 删除某个指明的handler if (fn) { let cb let i = cbs.length // 遍历该类型的callbacks,找到后splice()删除 while (i--) { cb = cbs[i] if (cb === fn || cb.fn === fn) { cbs.splice(i, 1) break } } } return vm }
Vue.prototype.$once
1 2 3 4 5 6 7 8 9 10 11 12 13 14
// Vue.$once实现,只执行一次 Vue.prototype.$once = function (event: string, fn: Function): Component{ const vm: Component = this
// Vue.$emit实现,触发某个事件 Vue.prototype.$emit = function (event: string): Component{ const vm: Component = this
// 提示使用event名称小写,可以结合烤肉串命名法 if (process.env.NODE_ENV !== 'production') { const lowerCaseEvent = event.toLowerCase() if (lowerCaseEvent !== event && vm._events[lowerCaseEvent]) { tip( `Event "${lowerCaseEvent}" is emitted in component ` + `${formatComponentName(vm)} but the handler is registered for "${event}". ` + `Note that HTML attributes are case-insensitive and you cannot use ` + `v-on to listen to camelCase events when using in-DOM templates. ` + `You should probably use "${hyphenate(event)}" instead of "${event}".`, ) } }
// 遍历该类型下所有handler,逐个执行 let cbs = vm._events[event] if (cbs) { cbs = cbs.length > 1 ? toArray(cbs) : cbs const args = toArray(arguments, 1) for (let i = 0, l = cbs.length; i < l; i++) { try { cbs[i].apply(vm, args) } catch (e) { handleError(e, vm, `event handler for "${event}"`) } } } return vm }