proxy 函数的定义非常重要,在下面要探究的各个初始化函数中它,它会将我们在配置对象中设置的属性全部定义到实例对象中,但是我们对这些属性的操作是通过各部分相应的代理属性上来执行的。get 和 set 方法的实现非常明白的表示出这一过程,然后再将属性定义到实例中。由这个函数作为基础,继续来看看其他五个状态的初始化函数的内容。
// 定义initProps函数,接收vm,propsOptions两个参数 functioninitProps (vm: Component, propsOptions: Object) { // 赋值propsData,propsData是全局扩展传入的赋值对象 // 在使用extend的时候会用到,实际开发里运用较少 const propsData = vm.$options.propsData || {} // 定义实例的_props私有属性,并赋值给props const props = vm._props = {} // 缓存prop键,以便将来props更新可以使用Array而不是动态对象键枚举进行迭代。 // cache prop keys so that future props updates can iterate using Array // instead of dynamic object key enumeration. const keys = vm.$options._propKeys = [] // 是否是根实例 const isRoot = !vm.$parent // 对于非根实例,关闭观察标识 // root instance props should be converted if (!isRoot) { toggleObserving(false) } // 遍历props配置对象 for (const key in propsOptions) { // 向缓存键值数组中添加键名 keys.push(key) // 验证prop的值,validateProp执行对初始化定义的props的类型检查和默认赋值 // 如果有定义类型检查,布尔值没有默认值时会被赋予false,字符串默认undefined // 对propsOptions的比较也是在使用extend扩展时才有意义 // 具体实现可以参考 src/core/util/props.js,没有难点这里不详细解释 const value = validateProp(key, propsOptions, propsData, vm)
// 非生产环境下进行检查和提示 /* 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 (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()方法扩展属性时,已经对静态属性进行了代理 // 这里只需要针对实例化时的属性执行代理操作 // static props are already proxied on the component's prototype // during Vue.extend(). We only need to proxy props defined at // instantiation here. // 当实例上没有同名属性时,对属性进行代理操作 // 将对键名的引用指向vm._props对象中 if (!(key in vm)) { proxy(vm, `_props`, key) } } // 开启观察状态标识 toggleObserving(true) }
// 定义initComputed函数,接受实例vm,和computed对象 functioninitComputed (vm: Component, computed: Object) { // $flow-disable-line // 定义watchers和实例_computedWatchers属性,初始赋值空对象 const watchers = vm._computedWatchers = Object.create(null) // 是否是服务器渲染,computed属性在服务器渲染期间只能是getter // computed properties are just getters during SSR const isSSR = isServerRendering()
// 遍历computed for (const key in computed) { // 获取用户定义的值 const userDef = computed[key] // 如果用户定义的是函数则赋值给getter否则j将userDef.get方法赋值给getter 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. // 键名非实例根属性时,定义计算属性,具体参照defineComputed函数 if (!(key in vm)) { defineComputed(vm, key, userDef) // 非生产环境下,检测与data属性名的冲突并给出警告 } 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) } } } }
// 定义并导出defineComputed哈数 // 接收实例target,计算属性键名key,计算属性值userDef参数 exportfunctiondefineComputed ( target: any, key: string, userDef: Object | Function ) { // 在非服务器渲染下设置缓存 const shouldCache = !isServerRendering() // 计算属性值是函数时 if (typeof userDef === 'function') { // 设置计算属性的getter,setter为空函数 sharedPropertyDefinition.get = shouldCache ? createComputedGetter(key) : userDef sharedPropertyDefinition.set = noop } else { // 当计算属性是对象时,设置计算属性的getter和setter sharedPropertyDefinition.get = userDef.get ? shouldCache && userDef.cache !== false ? createComputedGetter(key) : userDef.get : noop sharedPropertyDefinition.set = userDef.set ? userDef.set : noop } // 非生产环境下,如果没哟定义计算属性的setter // 想设置计算属性时给出警告 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) }
// 定义initMethods方法,接受实例vm,配置属性methods functioninitMethods (vm: Component, methods: Object) { // 获取实例的props const props = vm.$options.props // 遍历methods对象 for (const key in methods) { // 非生产环境下给出警告 if (process.env.NODE_ENV !== 'production') { // 未赋值方法警告 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 ) } // 与保留字冲突警告 if ((key in vm) && isReserved(key)) { warn( `Method "${key}" conflicts with an existing Vue instance method. ` + `Avoid defining component methods that start with _ or $.` ) } } // 在实例上定义方法,赋值为用户未定义函数或空函数 vm[key] = methods[key] == null ? noop : bind(methods[key], vm) } }
initMethods 函数非常简单,除了一大段在非生产环境里报告检查冲突的代码,唯一的内容就是在实例上定义相应的方法并且把上下文绑定到实例对象上,这样即便不是使用箭头函数,在方法内也默认用 this 指代了实例对象。