当前位置: 首页 > news >正文

最好的dm单网站建设哪里有做ppt的网站

最好的dm单网站建设,哪里有做ppt的网站,制作手机软件网站,自豪的采用wordpress文章目录 1. 前言2. initState函数分析3. 初始化props3.1 规范化数据3.2 initProps函数分析3.3 validateProp函数分析3.4 getPropDefaultValue函数分析3.5 assertProp函数分析 4. 初始化methods5. 初始化data6. 初始化computed6.1 回顾用法6.2 initComputed函数分析6.3 defineC… 文章目录 1. 前言2. initState函数分析3. 初始化props3.1 规范化数据3.2 initProps函数分析3.3 validateProp函数分析3.4 getPropDefaultValue函数分析3.5 assertProp函数分析 4. 初始化methods5. 初始化data6. 初始化computed6.1 回顾用法6.2 initComputed函数分析6.3 defineComputed函数分析6.4 createComputedGetter函数分析6.5 depend和evaluate 7. 初始化watch7.1 回顾用法7.2 initWatch函数分析7.3 createWatcher函数分析 8. 总结 1. 前言 本篇文章介绍生命周期初始化阶段所调用的第五个初始化函数——initState。 从函数名字上来看这个函数是用来初始化实例状态的那么什么是实例的状态呢在前面文章中我们略有提及在我们日常开发中在Vue组件中会写一些如props、data、methods、computed、watch选项我们把这些选项称为实例的状态选项。也就是说initState函数就是用来初始化这些状态的那么接下来我们就来分析该函数是如何初始化这些状态选项的。 2. initState函数分析 首先我们先来分析initState函数该函数的定义位于源码的src/core/instance/state.js中如下 export function initState (vm: Component) {vm._watchers []const opts vm.$optionsif (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)} }可以看到该函数的代码并不多而且逻辑也非常清晰。 首先给实例上新增了一个属性_watchers用来存储当前实例中所有的watcher实例无论是使用vm.$watch注册的watcher实例还是使用watch选项注册的watcher实例都会被保存到该属性中。 这里我们再额外多说一点在变化侦测篇中我们介绍了Vue中对数据变化的侦测是使用属性拦截的方式实现的但是Vue并不是对所有数据都使用属性拦截的方式侦测变化这是因为数据越多数据上所绑定的依赖就会多从而造成依赖追踪的内存开销就会很大所以从Vue 2.0版本起Vue不再对所有数据都进行侦测而是将侦测粒度提高到了组件层面对每个组件进行侦测所以在每个组件上新增了vm._watchers属性用来存放这个组件内用到的所有状态的依赖当其中一个状态发生变化时就会通知到组件然后由组件内部使用虚拟DOM进行数据比对从而降低内存开销提高性能。 继续回到源码接下来就是判断实例中有哪些选项就调用对应的选项初始化子函数进行初始化如下 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) }先判断实例中是否有props选项如果有就调用props选项初始化函数initProps去初始化props选项 再判断实例中是否有methods选项如果有就调用methods选项初始化函数initMethods去初始化methods选项 接着再判断实例中是否有data选项如果有就调用data选项初始化函数initData去初始化data选项如果没有就把data当作空对象并将其转换成响应式 接着再判断实例中是否有computed选项如果有就调用computed选项初始化函数initComputed去初始化computed选项 最后判断实例中是否有watch选项如果有就调用watch选项初始化函数initWatch去初始化watch选项 总之一句话就是有什么选项就调用对应的选项初始化子函数去初始化什么选项。 以上就是initState函数的所有逻辑其实你会发现在函数内部初始化这5个选项的时候它的顺序是有意安排的不是毫无章法的。如果你在开发中有注意到我们在data中可以使用props在watch中可以观察data和props之所以可以这样做就是因为在初始化的时候遵循了这种顺序先初始化props接着初始化data最后初始化watch。 下面我们就针对这5个状态选项对应的5个初始化子函数进行逐一分析看看其内部分别都是如何进行初始化的。 3. 初始化props props选项通常是由当前组件的父级组件传入的当父组件在调用子组件的时候通常会把props属性值作为标签属性添加在子组件的标签上如下 Child prop1xxx prop2yyy/Child在前面文章介绍初始化事件initEvents函数的时候我们说了在模板编译的时候当解析到组件标签时会将所有的标签属性都解析出来然后在子组件实例化的时候传给子组件当然这里面就包括props数据。 在子组件内部通过props选项来接收父组件传来的数据在接收的时候可以这样写 // 写法一 props: [name]// 写法二 props: {name: String, // [String, Number] }// 写法三 props: {name:{type: String} }可以看到Vue给用户提供的props选项写法非常自由根据Vue的惯例写法虽多但是最终处理的时候肯定只处理一种写法此时你肯定会想到处理之前先对数据进行规范化将所有写法都转化成一种写法。对你没有猜错同规范化事件一样在合并属性的时候也进行了props数据的规范化。 3.1 规范化数据 props数据规范化函数的定义位于源码的src/core/util/options.js中如下 function normalizeProps (options, vm) {const props options.propsif (!props) returnconst res {}let i, val, nameif (Array.isArray(props)) {i props.lengthwhile (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选项如果不存在则直接返回。 const props options.props if (!props) return如果存在则定义一个空对象res用来存储最终的结果。接着判断如果props选项是一个数组写法一则遍历该数组中的每一项元素如果该元素是字符串那么先将该元素统一转化成驼峰式命名然后将该元素作为key将{type: null}作为value存入res中如果该元素不是字符串则抛出异常。如下 if (Array.isArray(props)) {i props.lengthwhile (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.)}} }如果props选项不是数组那就继续判断是不是一个对象如果是一个对象那就遍历对象中的每一对键值拿到每一对键值后先将键名统一转化成驼峰式命名然后判断值是否还是一个对象如果值是对象写法三那么就将该键值对存入res中如果值不是对象写法二那么就将键名作为key将{type: null}作为value存入res中。如下 if (isPlainObject(props)) {for (const key in props) {val props[key]name camelize(key)res[name] isPlainObject(val)? val: { type: val }} }如果props选项既不是数组也不是对象那么如果在非生产环境下就抛出异常最后将res作为规范化后的结果重新赋值给实例的props选项。如下 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数据的规范化处理可以看到无论是三种写法的哪一种最终都会被转化成如下写法 props: {name:{type: xxx} }3.2 initProps函数分析 将props选项规范化完成之后接下来我们就可以来真正的初始化props选项了initProps函数的定义位于源码的src/core/instance/state.js中如下 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.const keys vm.$options._propKeys []const isRoot !vm.$parent// root instance props should be convertedif (!isRoot) {toggleObserving(false)}for (const key in propsOptions) {keys.push(key)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 props value. Prop being mutated: ${key},vm)}})} else {defineReactive(props, key, value)}// static props are already proxied on the components prototype// during Vue.extend(). We only need to proxy props defined at// instantiation here.if (!(key in vm)) {proxy(vm, _props, key)}}toggleObserving(true) }可以看到该函数接收两个参数当前Vue实例和当前实例规范化后的props选项。 在函数内部首先定义了4个变量分别是 const propsData vm.$options.propsData || {} const props vm._props {} const keys vm.$options._propKeys [] const isRoot !vm.$parentpropsData:父组件传入的真实props数据。props:指向vm._props的指针所有设置到props变量中的属性都会保存到vm._props中。keys:指向vm.$options._propKeys的指针缓存props对象中的key将来更新props时只需遍历vm.$options._propKeys数组即可得到所有props的key。isRoot:当前组件是否为根组件。 接着判断当前组件是否为根组件如果不是那么不需要将props数组转换为响应式的toggleObserving(false)用来控制是否将数据转换成响应式。如下 if (!isRoot) {toggleObserving(false) }接着遍历props选项拿到每一对键值先将键名添加到keys中然后调用validateProp函数关于该函数下面会介绍校验父组件传入的props数据类型是否匹配并获取到传入的值value然后将键和值通过defineReactive函数添加到props即vm._props中如下 for (const key in propsOptions) {keys.push(key)const value validateProp(key, propsOptions, propsData, vm)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 props value. Prop being mutated: ${key},vm)}})} else {defineReactive(props, key, value)}}添加完之后再判断这个key在当前实例vm中是否存在如果不存在则调用proxy函数在vm上设置一个以key为属性的代码当使用vm[key]访问数据时其实访问的是vm._props[key]。如下 if (!(key in vm)) {proxy(vm, _props, key) }以上就是initProps函数的所有逻辑接下来我们再看一下是如何通过validateProp函数校验父组件传入的props数据类型是否匹配并获取到传入的值的。 3.3 validateProp函数分析 validateProp函数的定义位于源码的src/core/util/props.js中如下 export function validateProp (key,propOptions,propsData,vm) {const prop propOptions[key]const absent !hasOwn(propsData, key)let value propsData[key]// boolean castingconst booleanIndex getTypeIndex(Boolean, prop.type)if (booleanIndex -1) {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 priorityconst stringIndex getTypeIndex(String, prop.type)if (stringIndex 0 || booleanIndex stringIndex) {value true}}}// check default valueif (value undefined) {value getPropDefaultValue(vm, prop, key)// since the default value is a fresh copy,// make sure to observe it.const prevShouldObserve shouldObservetoggleObserving(true)observe(value)toggleObserving(prevShouldObserve)}if (process.env.NODE_ENV ! production) {assertProp(prop, key, value, vm, absent)}return value }可以看到该函数接收4个参数分别是 key:遍历propOptions时拿到的每个属性名。propOptions:当前实例规范化后的props选项。propsData:父组件传入的真实props数据。vm:当前实例。 在函数内部首先定义了3个变量分别是 const prop propOptions[key] const absent !hasOwn(propsData, key) let value propsData[key]prop:当前key在propOptions中对应的值。absent:当前key是否在propsData中存在即父组件是否传入了该属性。value:当前key在propsData中对应的值即父组件对于该属性传入的真实值。 接着判断prop的type属性是否是布尔类型Boolean,getTypeIndex函数用于判断prop的type属性中是否存在某种类型如果存在则返回该类型在type属性中的索引因为type属性可以是数组如果不存在则返回-1。 如果是布尔类型的话那么有两种边界情况需要单独处理 如果absent为true即父组件没有传入该prop属性并且该属性也没有默认值的时候将该属性值设置为false如下 if (absent !hasOwn(prop, default)) {value false }如果父组件传入了该prop属性那么需要满足以下几点 该属性值为空字符串或者属性值与属性名相等prop的type属性中不存在String类型如果prop的type属性中存在String类型那么Boolean类型在type属性中的索引必须小于String类型的索引即Boolean类型的优先级更高; 则将该属性值设置为true如下 if (value || value hyphenate(key)) {const stringIndex getTypeIndex(String, prop.type)if (stringIndex 0 || booleanIndex stringIndex) {value true} }另外在判断属性值与属性名相等的时候是先将属性名由驼峰式转换成用-连接的字符串下面的这几种写法子组件的prop都将被设置为true Child name/Child Child namename/Child Child userNameuser-name/Child如果不是布尔类型是其它类型的话那就只需判断父组件是否传入该属性即可如果没有传入则该属性值为undefined此时调用getPropDefaultValue函数关于该函数下面会介绍获取该属性的默认值并将其转换成响应式如下 if (value undefined) {value getPropDefaultValue(vm, prop, key)// since the default value is a fresh copy,// make sure to observe it.const prevShouldObserve shouldObservetoggleObserving(true)observe(value)toggleObserving(prevShouldObserve) }如果父组件传入了该属性并且也有对应的真实值那么在非生产环境下会调用assertProp函数关于该函数下面会介绍校验该属性值是否与要求的类型相匹配。如下 if (process.env.NODE_ENV ! production ) {assertProp(prop, key, value, vm, absent) }最后将父组件传入的该属性的真实值返回。 3.4 getPropDefaultValue函数分析 getPropDefaultValue函数的定义位于源码的src/core/util/props.js中如下 function getPropDefaultValue (vm, prop, key){// no default, return undefinedif (!hasOwn(prop, default)) {return undefined}const def prop.default// warn against non-factory defaults for Object Arrayif (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 triggerif (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 contextreturn typeof def function getType(prop.type) ! Function? def.call(vm): def }该函数接收三个参数分别是 vm:当前实例prop:子组件props选项中的每个key对应的值key:子组件props选项中的每个key 其作用是根据子组件props选项中的key获取其对应的默认值。 首先判断prop中是否有default属性如果没有则表示没有默认值直接返回。如下 if (!hasOwn(prop, default)) {return undefined }如果有则取出default属性赋给变量def。接着判断在非生产环境下def是否是一个对象如果是则抛出警告对象或数组默认值必须从一个工厂函数获取。如下 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) }接着再判断如果父组件没有传入该props属性但是在vm._props中有该属性值这说明vm._props中的该属性值就是默认值如下 if (vm vm.$options.propsData vm.$options.propsData[key] undefined vm._props[key] ! undefined) {return vm._props[key] }最后判断def是否为函数并且prop.type不为Function如果是的话表明def是一个返回对象或数组的工厂函数那么将函数的返回值作为默认值返回如果def不是函数那么则将def作为默认值返回。如下 return typeof def function getType(prop.type) ! Function? def.call(vm): def3.5 assertProp函数分析 assertProp函数的定义位于源码的src/core/util/props.js中如下 function assertProp (prop,name,value,vm,absent) {if (prop.required absent) {warn(Missing required prop: name ,vm)return}if (value null !prop.required) {return}let type prop.typelet valid !type || type trueconst 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.expectedType || )valid assertedType.valid}}if (!valid) {warn(Invalid prop: type check failed for prop ${name}. Expected ${expectedTypes.map(capitalize).join(, )} , got ${toRawType(value)}.,vm)return}const validator prop.validatorif (validator) {if (!validator(value)) {warn(Invalid prop: custom validator check failed for prop name .,vm)}} }该函数接收5个参数分别是 prop:prop选项;name:props中prop选项的key;value:父组件传入的propsData中key对应的真实数据vm:当前实例absent:当前key是否在propsData中存在即父组件是否传入了该属性。 其作用是校验父组件传来的真实值是否与prop的type类型相匹配如果不匹配则在非生产环境下抛出警告。 函数内部首先判断prop中如果设置了必填项即prop.required为true并且父组件又没有传入该属性此时则抛出警告提示该项必填。如下 if (prop.required absent) {warn(Missing required prop: name ,vm)return }接着判断如果该项不是必填的并且该项的值value不存在那么此时是合法的直接返回。如下 if (value null !prop.required) {return }接下来定义了3个变量分别是 let type prop.type let valid !type || type true const expectedTypes []type:prop中的type类型valid校验是否成功expectedTypes保存期望类型的数组当校验失败抛出警告时会提示用户该属性所期望的类型是什么 通常情况下type可以是一个原生构造函数也可以是一个包含多种类型的数组还可以不设置该属性。如果用户设置的是原生构造函数或数组那么此时vaild默认为false!type如果用户没有设置该属性表示不需要校验那么此时vaild默认为true即校验成功。 另外当type等于true时即出现这样的写法props:{name:true}这说明prop一定会校验成功。所以当出现这种语法的时候此时type true所以vaild默认为true。 接下来开始校验类型如果用户设置了type属性则判断该属性是不是数组如果不是则统一转化为数组方便后续处理如下 if (type) {if (!Array.isArray(type)) {type [type]} }接下来遍历type数组并调用assertType函数校验value。assertType函数校验后会返回一个对象如下 {vaild:true, // 表示是否校验成功expectedTypeBoolean // 表示被校验的类型 }然后将被校验的类型添加到expectedTypes中并将vaild变量设置为assertedType.valid如下 for (let i 0; i type.length !valid; i) {const assertedType assertType(value, type[i])expectedTypes.push(assertedType.expectedType || )valid assertedType.valid }这里请注意上面循环中的条件语句有这样一个条件!vaild即type数组中还要有一个校验成功循环立即结束表示校验通过。 接下来如果循环完毕后vaild为false即表示校验未通过则抛出警告。如下 if (!valid) {warn(Invalid prop: type check failed for prop ${name}. Expected ${expectedTypes.map(capitalize).join(, )} , got ${toRawType(value)}.,vm)return }另外prop选项还支持自定义校验函数如下 props:{// 自定义验证函数propF: {validator: function (value) {// 这个值必须匹配下列字符串中的一个return [success, warning, danger].indexOf(value) ! -1}} }所以还需要使用用户传入的自定义校验函数来校验数据。首先获取到用户传入的校验函数调用该函数并将待校验的数据传入如果校验失败则抛出警告。如下 const validator prop.validator if (validator) {if (!validator(value)) {warn(Invalid prop: custom validator check failed for prop name .,vm)} }4. 初始化methods 初始化methods相较而言就比较简单了它的初始化函数定义位于源码的src/core/instance/state.js中如下 function initMethods (vm, methods) {const props vm.$options.propsfor (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)}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)} }从代码中可以看到初始化methods无非就干了三件事判断method有没有method的命名符不符合命名规范如果method既有又符合规范那就把它挂载到vm实例上。下面我们就逐行分析源码来过一遍这三件事。 首先遍历methods选项中的每一个对象在非生产环境下判断如果methods中某个方法只有key而没有value即只有方法名没有方法体时抛出异常提示用户方法未定义。如下 if (methods[key] null) {warn(Method ${key} has an undefined value in the component definition. Did you reference the function correctly?,vm) }接着判断如果methods中某个方法名与props中某个属性名重复了就抛出异常提示用户方法名重复了。如下 if (props hasOwn(props, key)) {warn(Method ${key} has already been defined as a prop.,vm) }接着判断如果methods中某个方法名如果在实例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 $.) }其中isReserved函数是用来判断字符串是否以_或$开头。 最后如果上述判断都没问题那就method绑定到实例vm上这样我们就可以通过this.xxx来访问methods选项中的xxx方法了如下 vm[key] methods[key] null ? noop : bind(methods[key], vm)5. 初始化data 初始化data也比较简单它的初始化函数定义位于源码的src/core/instance/state.js中如下 function initData (vm) {let data vm.$options.datadata 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 instanceconst keys Object.keys(data)const props vm.$options.propsconst methods vm.$options.methodslet i keys.lengthwhile (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, _data, key)}}// observe dataobserve(data, true /* asRootData */) }可以看到initData函数的逻辑并不复杂跟initMethods函数的逻辑有几分相似。就是通过一系列条件判断用户传入的data选项是否合法最后将data转换成响应式并绑定到实例vm上。下面我们就来仔细看一下代码逻辑。 首先获取到用户传入的data选项赋给变量data同时将变量data作为指针指向vm._data然后判断data是不是一个函数如果是就调用getData函数获取其返回值将其保存到vm._data中。如果不是就将其本身保存到vm._data中。如下 let data vm.$options.data data vm._data typeof data function? getData(data, vm): data || {}我们知道无论传入的data选项是不是一个函数它最终的值都应该是一个对象如果不是对象的话就抛出警告提示用户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) }接下来遍历data对象中的每一项在非生产环境下判断data对象中是否存在某一项的key与methods中某个属性名重复如果存在重复就抛出警告提示用户属性名重复。如下 if (process.env.NODE_ENV ! production) {if (methods hasOwn(methods, key)) {warn(Method ${key} has already been defined as a data property.,vm)} }接着再判断是否存在某一项的key与prop中某个属性名重复如果存在重复就抛出警告提示用户属性名重复。如下 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) }如果都没有重复则调用proxy函数将data对象中key不以_或$开头的属性代理到实例vm上这样我们就可以通过this.xxx来访问data选项中的xxx数据了。如下 if (!isReserved(key)) {proxy(vm, _data, key) }最后调用observe函数将data中的数据转化成响应式如下 observe(data, true /* asRootData */)6. 初始化computed 计算属性computed相信大家一定不会陌生在日常开发中肯定会经常用到而且我们知道计算属性有一个很大的特点就是 计算属性的结果会被缓存除非依赖的响应式属性变化才会重新计算。 那么接下来我们就来看一下计算属性是如何实现这些功能的的。 6.1 回顾用法 首先根据官方文档的使用示例我们来回顾一下计算属性的用法如下 var vm new Vue({data: { a: 1 },computed: {// 仅读取aDouble: function () {return this.a * 2},// 读取和设置aPlus: {get: function () {return this.a 1},set: function (v) {this.a v - 1}}} }) vm.aPlus // 2 vm.aPlus 3 vm.a // 2 vm.aDouble // 4可以看到computed选项中的属性值可以是一个函数那么该函数默认为取值器getter用于仅读取数据还可以是一个对象对象里面有取值器getter和存值器setter用于读取和设置数据。 6.2 initComputed函数分析 了解了计算属性的用法之后下面我们就来分析一下计算属性的初始化函数initComputed的内部原理是怎样的。initComputed函数的定义位于源码的src/core/instance/state.js中如下 function initComputed (vm: Component, computed: Object) {const watchers vm._computedWatchers Object.create(null)const isSSR isServerRendering()for (const key in computed) {const userDef computed[key]const getter typeof userDef function ? userDef : userDef.getif (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)}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)}}} }可以看到在函数内部首先定义了一个变量watchers并将其赋值为空对象同时将其作为指针指向vm._computedWatchers如下 const watchers vm._computedWatchers Object.create(null)接着遍历computed选项中的每一项属性首先获取到每一项的属性值记作userDef然后判断userDef是不是一个函数如果是函数则该函数默认为取值器getter将其赋值给变量getter如果不是函数则说明是一个对象则取对象中的get属性作为取值器赋给变量getter。如下 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) }接着判断如果不是在服务端渲染环境下则创建一个watcher实例并将当前循环到的的属性名作为键创建的watcher实例作为值存入watchers对象中。如下 if (!isSSR) {// create internal watcher for the computed property.watchers[key] new Watcher(vm,getter || noop,noop,computedWatcherOptions) }最后判断当前循环到的的属性名是否存在于当前实例vm上如果存在则在非生产环境下抛出警告如果不存在则调用defineComputed函数为实例vm上设置计算属性。 以上就是initComputed函数的内部逻辑接下里我们再来看一下defineComputed函数是如何为实例vm上设置计算属性的。 6.3 defineComputed函数分析 defineComputed函数的定义位于源码的src/core/instance/state.js中如下 const sharedPropertyDefinition {enumerable: true,configurable: true,get: noop,set: noop }export function defineComputed (target,key,userDef) {const shouldCache !isServerRendering()if (typeof userDef function) {sharedPropertyDefinition.get shouldCache? createComputedGetter(key): userDefsharedPropertyDefinition.set noop} else {sharedPropertyDefinition.get userDef.get? shouldCache userDef.cache ! false? createComputedGetter(key): userDef.get: noopsharedPropertyDefinition.set userDef.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) }该函数接受3个参数分别是target、key和userDef。其作用是为target上定义一个属性key并且属性key的getter和setter根据userDef的值来设置。下面我们就来看一下该函数的具体逻辑。 首先定义了变量sharedPropertyDefinition它是一个默认的属性描述符。 接着在函数内部定义了变量shouldCache用于标识计算属性是否应该有缓存。该变量的值是当前环境是否为非服务端渲染环境如果是非服务端渲染环境则该变量为true。也就是说只有在非服务端渲染环境下计算属性才应该有缓存。如下 const shouldCache !isServerRendering()接着判断如果userDef是一个函数则该函数默认为取值器getter此处在非服务端渲染环境下并没有直接使用userDef作为getter而是调用createComputedGetter函数关于该函数下面会介绍创建了一个getter这是因为userDef只是一个普通的getter它并没有缓存功能所以我们需要额外创建一个具有缓存功能的getter而在服务端渲染环境下可以直接使用userDef作为getter因为在服务端渲染环境下计算属性不需要缓存。由于用户没有设置setter函数所以将sharedPropertyDefinition.set设置为noop。如下 if (typeof userDef function) {sharedPropertyDefinition.get shouldCache? createComputedGetter(key): userDefsharedPropertyDefinition.set noop }如果userDef不是一个函数那么就将它当作对象处理。在设置sharedPropertyDefinition.get的时候先判断userDef.get是否存在如果不存在则将其设置为noop如果存在则同上面一样在非服务端渲染环境下并且用户没有明确的将userDef.cache设置为false时调用createComputedGetter函数创建一个getter赋给sharedPropertyDefinition.get。然后设置sharedPropertyDefinition.set为userDef.set函数。如下 sharedPropertyDefinition.get userDef.get? shouldCache userDef.cache ! false? createComputedGetter(key): userDef.get: noop sharedPropertyDefinition.set userDef.set? userDef.set: noop接着再判断在非生产环境下如果用户没有设置setter的话那么就给setter一个默认函数这是为了防止用户在没有设置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方法将属性key绑定到target上其中的属性描述符就是上面设置的sharedPropertyDefinition。如此以来就将计算属性绑定到实例vm上了。 以上就是defineComputed函数的所有逻辑。另外我们发现计算属性有没有缓存及其响应式貌似主要在于是否将getter设置为createComputedGetter函数的返回结果。那么接下来我们就对这个createComputedGetter函数一探究竟。 6.4 createComputedGetter函数分析 createComputedGetter函数的定义位于源码的src/core/instance/state.js中如下 function createComputedGetter (key) {return function computedGetter () {const watcher this._computedWatchers this._computedWatchers[key]if (watcher) {watcher.depend()return watcher.evaluate()}} }可以看到该函数是一个高阶函数其内部返回了一个computedGetter函数所以其实是将computedGetter函数赋给了sharedPropertyDefinition.get。当获取计算属性的值时会执行属性的getter而属性的getter就是 sharedPropertyDefinition.get也就是说最终执行的 computedGetter函数。 在computedGetter函数内部首先存储在当前实例上_computedWatchers属性中key所对应的watcher实例如果watcher存在则调用watcher实例上的depend方法和evaluate方法并且将evaluate方法的返回值作为计算属性的计算结果返回。那么watcher实例上的depend方法和evaluate方法又是什么呢 6.5 depend和evaluate 回顾上文中创建watcher实例的时候 const computedWatcherOptions { computed: true } watchers[key] new Watcher(vm,getter || noop,noop,computedWatcherOptions )传入的参数中第二个参数是getter函数第四个参数是一个对象computedWatcherOptions。 我们再回顾Watcher类的定义如下 export default class Watcher {constructor (vm,expOrFn,cb,options,isRenderWatcher) {if (options) {// ...this.computed !!options.computed// ...} else {// ...}this.dirty this.computed // for computed watchersif (typeof expOrFn function) {this.getter expOrFn}if (this.computed) {this.value undefinedthis.dep new Dep()}}evaluate () {if (this.dirty) {this.value this.get()this.dirty false}return this.value}/*** Depend on this watcher. Only for computed property watchers.*/depend () {if (this.dep Dep.target) {this.dep.depend()}}update () {if (this.computed) {if (this.dep.subs.length 0) {this.dirty true} else {this.getAndInvoke(() {this.dep.notify()})}}}getAndInvoke (cb: Function) {const value this.get()if (value ! this.value ||// Deep watchers and watchers on Object/Arrays should fire even// when the value is the same, because the value may// have mutated.isObject(value) ||this.deep) {// set new valueconst oldValue this.valuethis.value valuethis.dirty falseif (this.user) {try {cb.call(this.vm, value, oldValue)} catch (e) {handleError(e, this.vm, callback for watcher ${this.expression})}} else {cb.call(this.vm, value, oldValue)}}} }可以看到在实例化Watcher类的时候第四个参数传入了一个对象computedWatcherOptions { computed: true }该对象中的computed属性标志着这个watcher实例是计算属性的watcher实例即Watcher类中的this.computed属性同时类中还定义了this.dirty属性用于标志计算属性的返回值是否有变化计算属性的缓存就是通过这个属性来判断的每当计算属性依赖的数据发生变化时会将this.dirty属性设置为true这样下一次读取计算属性时会重新计算结果返回否则直接返回之前的计算结果。 当调用watcher.depend()方法时会将读取计算属性的那个watcher添加到计算属性的watcher实例的依赖列表中当计算属性中用到的数据发生变化时计算属性的watcher实例就会执行watcher.update()方法在update方法中会判断当前的watcher是不是计算属性的watcher如果是则调用getAndInvoke去对比计算属性的返回值是否发生了变化如果真的发生变化则执行回调通知那些读取计算属性的watcher重新执行渲染逻辑。 当调用watcher.evaluate()方法时会先判断this.dirty是否为true如果为true则表明计算属性所依赖的数据发生了变化则调用this.get()重新获取计算结果最后返回如果为false则直接返回之前的计算结果。 其内部原理如图所示 7. 初始化watch 接下来就是最后一个初始化函数了——初始化watch选项。在日常开发中watch选项也经常会使用到它可以用来侦听某个已有的数据当该数据发生变化时执行对应的回调函数。那么接下来我们就来看一些watch选项是如何被初始化的。 7.1 回顾用法 首先根据官方文档的使用示例我们来回顾一下watch选项的用法如下 var vm new Vue({data: {a: 1,b: 2,c: 3,d: 4,e: {f: {g: 5}}},watch: {a: function (val, oldVal) {console.log(new: %s, old: %s, val, oldVal)},// methods选项中的方法名b: someMethod,// 深度侦听该回调会在任何被侦听的对象的 property 改变时被调用不论其被嵌套多深c: {handler: function (val, oldVal) { /* ... */ },deep: true},// 该回调将会在侦听开始之后被立即调用d: {handler: someMethod,immediate: true},// 调用多个回调e: [handle1,function handle2 (val, oldVal) { /* ... */ },{handler: function handle3 (val, oldVal) { /* ... */ },}],// 侦听表达式e.f: function (val, oldVal) { /* ... */ }} }) vm.a 2 // new: 2, old: 1可以看到watch选项的用法非常灵活。首先watch选项是一个对象键是需要观察的表达式值是对应回调函数。值也可以是方法名或者包含选项的对象。既然给用户提供的用法灵活那么在代码中就需要按条件来判断根据不同的用法做相应的处理。 7.2 initWatch函数分析 了解了watch选项的用法之后下面我们就来分析一下watch选项的初始化函数initWatch的内部原理是怎样的。initWatch函数的定义位于源码的src/core/instance/state.js中如下 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)}} }可以看到在函数内部会遍历watch选项拿到每一项的key和对应的值handler。然后判断handler是否为数组如果是数组则循环该数组并将数组中的每一项依次调用createWatcher函数来创建watcher如果不是数组则直接调用createWatcher函数来创建watcher。那么这个createWatcher函数是如何创建watcher的呢 7.3 createWatcher函数分析 createWatcher函数的定义位于源码的src/core/instance/state.js中如下 function createWatcher (vm: Component,expOrFn: string | Function,handler: any,options?: Object ) {if (isPlainObject(handler)) {options handlerhandler handler.handler}if (typeof handler string) {handler vm[handler]}return vm.$watch(expOrFn, handler, options) }可以看到该函数接收4个参数分别是 vm:当前实例expOrFn:被侦听的属性表达式handler:watch选项中每一项的值options:用于传递给vm.$watch的选项对象 在该函数内部首先会判断传入的handler是否为一个对象如果是一个对象那么就认为用户使用的是这种写法 watch: {c: {handler: function (val, oldVal) { /* ... */ },deep: true} }即带有侦听选项的写法此时就将handler对象整体记作options把handler对象中的handler属性作为真正的回调函数记作handler如下 if (isPlainObject(handler)) {options handlerhandler handler.handler }接着判断传入的handler是否为一个字符串如果是一个字符串那么就认为用户使用的是这种写法 watch: {// methods选项中的方法名b: someMethod, }即回调函数是methods选项中的一个方法名我们知道在初始化methods选项的时候会将选项中的每一个方法都绑定到当前实例上所以此时我们只需从当前实例上取出该方法作为真正的回调函数记作handler如下 if (typeof handler string) {handler vm[handler] }如果既不是对象又不是字符串那么我们就认为它是一个函数就不做任何处理。 针对不同类型的值处理完毕后expOrFn是被侦听的属性表达式handler变量是回调函数options变量为侦听选项最后调用vm.$watcher方法关于该方法在介绍全局实例方法的时候会详细介绍并传入以上三个参数完成初始化watch。 8. 总结 本篇文章介绍了生命周期初始化阶段所调用的第五个初始化函数——initState。该初始化函数内部总共初始化了5个选项分别是props、methods、data、computed和watch。 这5个选项的初始化顺序不是任意的而是经过精心安排的。只有按照这种顺序初始化我们才能在开发中在data中可以使用props在watch中可以观察data和props。 这5个选项中的所有属性最终都会被绑定到实例上这也就是我们为什么可以使用this.xxx来访问任意属性。同时正是因为这一点这5个选项中的所有属性名都不应该有所重复这样会造成属性之间相互覆盖。 最后我们对这5个选项分别都是如何进行初始化的内部原理进行了逐一分析。
http://www.zqtcl.cn/news/771640/

相关文章:

  • 织梦 修改网站logo营销型网站设计的内容
  • 电商网站运营策划做网站CentOS还是win好
  • 小型企业网站模板企业网站seo点击软件
  • 提供邯郸企业建网站网站图片上怎么做弹幕效果
  • 滨州做网站的wordpress如何添加商桥
  • 网站登录密码忘记网站开发营业执照申请
  • 电商网站设计思路音乐推广平台有哪些
  • 网站建设傲鸿网站链轮内有死链
  • 哪些网站可以做微商品牌宣传网站怎么不花钱做排名 知乎
  • 上传了网站源码怎么做wordpress加百度广告代码出问题
  • 哪些网站做推广vi设计说明模板
  • 杭州市建设工程造价管理协会网站攀枝花建设工程质量监督站投诉网站
  • 做网站推广送什么深圳的网站建设公司流程
  • 中国网站开发的前景制作公司主页网站
  • 在线画流程图的网站购物网站的设计与实现论文
  • 淘宝客cms网站建设K12网站怎么建设
  • 专业门户网站开发浙江省湖州艺术与设计学校官网
  • 企业网站搭建价格搭建平台的另一种说法
  • 网站开发框架桂林人论坛风姿摄影
  • 吉林省建设安全信息网站网站服务器和空间有什么区别
  • 百度制作网站怎么去掉2345网址导航
  • 深圳网站建设有限公司 2019哪些建材网站可以做宣传
  • 西安阿里云网站建设一建报名资格条件
  • 聊城网站优化wordpress循环该分类子分类
  • 帮网站做关键词排名优化创造网站需要多少钱
  • 广西网站建设推荐wordpress 宣布停止
  • 专注网站制作青岛景观设计公司排名
  • 安庆做网站网站代理建设网站观澜
  • 网站开发需求收集 模板cms做门户网站
  • dw网站首页的导航怎么做有大佬给个网址吗