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

【Vue2.0源码学习】生命周期篇-初始化阶段(initState)

文章目录

    • 1. 前言
    • 2. initState函数分析
    • 3. 初始化props
      • 3.1 规范化数据
      • 3.2 initProps函数分析
      • 3.3 validateProp函数分析
      • 3.4 getPropDefaultValue函数分析
      • 3.5 assertProp函数分析
    • 4. 初始化methods
    • 5. 初始化data
    • 6. 初始化computed
      • 6.1 回顾用法
      • 6.2 initComputed函数分析
      • 6.3 defineComputed函数分析
      • 6.4 createComputedGetter函数分析
      • 6.5 depend和evaluate
    • 7. 初始化watch
      • 7.1 回顾用法
      • 7.2 initWatch函数分析
      • 7.3 createWatcher函数分析
    • 8. 总结

1. 前言

本篇文章介绍生命周期初始化阶段所调用的第五个初始化函数——initState。 从函数名字上来看,这个函数是用来初始化实例状态的,那么什么是实例的状态呢?在前面文章中我们略有提及,在我们日常开发中,在Vue组件中会写一些如propsdatamethodscomputedwatch选项,我们把这些选项称为实例的状态选项。也就是说,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中可以观察dataprops,之所以可以这样做,就是因为在初始化的时候遵循了这种顺序,先初始化props,接着初始化data,最后初始化watch

下面我们就针对这5个状态选项对应的5个初始化子函数进行逐一分析,看看其内部分别都是如何进行初始化的。

3. 初始化props

props选项通常是由当前组件的父级组件传入的,当父组件在调用子组件的时候,通常会把props属性值作为标签属性添加在子组件的标签上,如下:

<Child prop1="xxx" prop2="yyy"></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 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)
}

可以看到,该函数接收两个参数:当前Vue实例和当前实例规范化后的props选项。

在函数内部首先定义了4个变量,分别是:

const propsData = vm.$options.propsData || {}
const props = vm._props = {}
const keys = vm.$options._propKeys = []
const isRoot = !vm.$parent
  • propsData:父组件传入的真实props数据。
  • props:指向vm._props的指针,所有设置到props变量中的属性都会保存到vm._props中。
  • keys:指向vm.$options._propKeys的指针,缓存props对象中的key,将来更新props时只需遍历vm.$options._propKeys数组即可得到所有propskey
  • 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 prop's ` +`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:当前keypropOptions中对应的值。
  • absent:当前key是否在propsData中存在,即父组件是否传入了该属性。
  • value:当前keypropsData中对应的值,即父组件对于该属性传入的真实值。

接着,判断proptype属性是否是布尔类型(Boolean),getTypeIndex函数用于判断proptype属性中是否存在某种类型,如果存在,则返回该类型在type属性中的索引(因为type属性可以是数组),如果不存在则返回-1。

如果是布尔类型的话,那么有两种边界情况需要单独处理:

  1. 如果absenttrue,即父组件没有传入该prop属性并且该属性也没有默认值的时候,将该属性值设置为false,如下:

    if (absent && !hasOwn(prop, 'default')) {value = false
    }
    
  2. 如果父组件传入了该prop属性,那么需要满足以下几点:

    • 该属性值为空字符串或者属性值与属性名相等;
    • proptype属性中不存在String类型;
    • 如果proptype属性中存在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 name="name"></Child>
    <Child userName="user-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): def

3.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:propsprop选项的key;
  • value:父组件传入的propsDatakey对应的真实数据;
  • vm:当前实例;
  • absent:当前key是否在propsData中存在,即父组件是否传入了该属性。

其作用是校验父组件传来的真实值是否与proptype类型相匹配,如果不匹配则在非生产环境下抛出警告。

函数内部首先判断prop中如果设置了必填项(即prop.requiredtrue)并且父组件又没有传入该属性,此时则抛出警告:提示该项必填。如下:

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函数校验valueassertType函数校验后会返回一个对象,如下:

{vaild:true,       // 表示是否校验成功expectedType:'Boolean'   // 表示被校验的类型
}

然后将被校验的类型添加到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数组中还要有一个校验成功,循环立即结束,表示校验通过。

接下来,如果循环完毕后vaildfalse,即表示校验未通过,则抛出警告。如下:

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对象中是否存在某一项的keymethods中某个属性名重复,如果存在重复,就抛出警告:提示用户属性名重复。如下:

if (process.env.NODE_ENV !== 'production') {if (methods && hasOwn(methods, key)) {warn(`Method "${key}" has already been defined as a data property.`,vm)}
}

接着再判断是否存在某一项的keyprop中某个属性名重复,如果存在重复,就抛出警告:提示用户属性名重复。如下:

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个参数,分别是:targetkeyuserDef。其作用是为target上定义一个属性key,并且属性keygettersetter根据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.setuserDef.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个选项,分别是:propsmethodsdatacomputedwatch

这5个选项的初始化顺序不是任意的,而是经过精心安排的。只有按照这种顺序初始化我们才能在开发中在data中可以使用props,在watch中可以观察dataprops

这5个选项中的所有属性最终都会被绑定到实例上,这也就是我们为什么可以使用this.xxx来访问任意属性。同时正是因为这一点,这5个选项中的所有属性名都不应该有所重复,这样会造成属性之间相互覆盖。

最后,我们对这5个选项分别都是如何进行初始化的内部原理进行了逐一分析。

相关文章:

【Vue2.0源码学习】生命周期篇-初始化阶段(initState)

文章目录 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. 词法 1.1 名词 名词&#xff08;n.)&#xff0c;是词类的一种&#xff0c;属于实词。他表示人&#xff0c;物&#xff0c;事&#xff0c;地点或抽象概念的统一名称。 1.1 名词的含义 名词&#xff08;n.)&#xff0c;是词类的一种&#xff0c;属于实词。他表示人&#x…...

QUIC协议连接详解(二)

目录 一&#xff1a;RTT解释 二&#xff1a;QUIC 1-RTT连接 三&#xff1a;QUIC 0-RTT连接 一&#xff1a;RTT解释 在介绍QUIC协议的连接之前先科普一下什么是RTT。RTT是Round-Trip Time的英文缩写&#xff0c;翻译过来就是一趟来回的时间即往返时延。时间计算即从发送方发送…...

JAVA 经常遇到一些问题【第二部分36~51】

重拾者&#xff1a; 每日记录至目前&#xff08;记录51种不同场景的问题可参考解决方案&#xff09; 异常就两部分&#xff1a; 1、excepiton信息&#xff1a; 报错产生的原因 2、at开头表示&#xff1a; 异常产生的代码位置。 欢迎关注本人微信公众号&#xff1a;AIM…...

蓝桥杯打卡Day6

文章目录 N的阶乘基本算术整数查询 一、N的阶乘OI链接 本题思路&#xff1a;本题是关于高精度的模板题。 #pragma GCC optimize(3) #include <bits/stdc.h>constexpr int N1010;std::vector<int> a; std::vector<int> f[N];std::vector<int> mul(in…...

spark集群问题汇总

一、 磁盘问题 问题描述可能原因解决措施core节点磁盘不足, 并且持续增加未开启spark-history的日志清理打开日志清理: spark.history.fs.cleaner.enabled task节点磁盘不足 APP应用使用磁盘过大: 1. 严重的数据倾斜 2. 应用本身数据量大 1. 解决数据倾斜 2. 加大资源, 增加e…...

WebServer 解析HTTP 请求报文

一、TCP 状态转换 浏览器访问网址&#xff0c;TCP传输全过程 二、TCP协议的通信过程 三、TCP 通信流程 // TCP 通信的流程 // 服务器端 &#xff08;被动接受连接的角色&#xff09; 1. 创建一个用于监听的套接字- 监听&#xff1a;监听有客户端的连接- 套接字&#xff1a;这…...

Golang开发--interface的使用

在Go语言中&#xff0c;接口&#xff08;interface&#xff09;是一种特殊的类型&#xff0c;它定义了一组方法的集合。接口为实现多态性提供了一种机制&#xff0c;允许不同的数据类型实现相同的方法&#xff0c;从而可以以统一的方式处理这些不同类型的对象。接口在Go中广泛用…...

2023 年高教社杯全国大学生数学建模竞赛题目 B 题 多波束测线问题

B 题 多波束测线问题 单波束测深是利用声波在水中的传播特性来测量水体深度的技术。声波在均匀介质中作匀速直线传播&#xff0c;在不同界面上产生反射&#xff0c;利用这一原理&#xff0c;从测量船换能器垂直向海底发射声波信号&#xff0c;并记录从声波发射到信号接收的传播…...

leetcode算法题--生成特殊数字的最少操作

原题链接&#xff1a;https://leetcode.cn/problems/minimum-operations-to-make-a-special-number/description/ 感觉还是比较难想到的。。 func minimumOperations(num string) int {res : len(num)if strings.Contains(num, "0") {res-- }f : func(tail string)…...

数学建模--决策树的预测模型的Python实现

目录 1.算法流程简介 2.算法核心代码 3.算法效果展示 1.算法流程简介 """ 决策树的应用:对泰坦尼克号数据集成员进行预测生死 算法流程还是比较简单的,简单学习一下决策树跟着注释写即可 文章参考:https://zhuanlan.zhihu.com/p/133838427 算法种遇上sklear…...

Linkstech多核并行仿真丨光伏发电系统模型及IEEE 39 bus模型多核并行实测

新能源场站和区域电网作为复杂且具有动态特性的大规模电力系统&#xff0c;需要实时仿真测试来验证其性能、稳定性和响应能力。在这种背景下&#xff0c;多核并行仿真运算显得尤为重要。多核并行仿真能够同时处理电力系统的复杂模型&#xff0c;加速仿真过程&#xff0c;实现接…...

在STS里使用Gradle编译Apache POI5.0.0

1、到官方下面地址下载Gradle最新的版本 Gradle Distributions 2、解压后拷贝到D盘下D:\gradle-8.3-rc-4里 3、配置环境变量 新建系统变量 GRADLE_HOME &#xff0c;值为 路径 4、在 Path 中添加上面目录的 bin 文件路径 &#xff08;可以用 %GRADLE_HOME%\bin&#xff0c…...

golang - 使用有缓冲通道控制并发数

在 Go 语言中&#xff0c;使用带缓冲的通道&#xff08;buffered channels&#xff09;可以有效地控制并发数。带缓冲的通道可以让你限制同时运行的 goroutine 数量&#xff0c;从而避免过度并发导致的资源耗尽问题。以下是一个使用带缓冲通道控制并发数的示例&#xff1a; pa…...

AUTOSAR测试指标

测试方法 1、测试相关时间2、检查各个状态下ECU的情况3、程序编写 1、测试相关时间 序号时间参数描述测试方法时间1T_Wakeup从睡眠模式到网络模式&#xff0c;(上位机)发送NM报文的时间唤醒源的时间100ms2T_START_NM从睡眠模式到网络模式&#xff0c;DUT发送的第一帧NM报文捕获…...

Vue 前端项目使用alibaba矢量库svg图标

Vue 前端项目使用alibaba矢量库svg图标 这里主要是记录 vue项目中使用阿里矢量库图标的操作流程&#xff0c;方便以后查阅&#xff01;&#xff01;&#xff01; 一、简介 iconfont 是由阿里巴巴体验团队打造的&#xff0c;一款设计和前端开发的便捷工具.拥有着很强大且图标内…...

蓝桥杯官网填空题(距离和)

题目描述 本题为填空题&#xff0c;只需要算出结果后&#xff0c;在代码中使用输出语句将所填结果输出即可。 两个字母之间的距离定义为它们在字母表中位置的距离。例如 A 和 C 的距离为 2&#xff0c;L 和 Q 的距离为 5。 对于一个字符串&#xff0c;我们称字符串中两两字符…...

【座位调整】Python 实现-附ChatGPT解析

疫情期间课堂的座位进行了特殊的调整,不能出现两个同学紧挨着,必须隔至少一个空位,给你一个整数数组desk,表示当前座位的占座情况,由若于0和1组成,其中 0 表示没有占位,1表示占位。在不改变原有座位秩序情况下,还能安排坐几个人? 输入描述: 第一行是一个数组,表示作为…...

前端面试基础面试题——5

1.react 和 vue 的区别是什么&#xff1f; 2.虚拟DOM的原理&#xff1f;优点与缺点&#xff1f; 3.类组件和函数组件之间的区别是&#xff1f; 4.state 和 props 区别是什么&#xff1f; 5.React 中 refs 是做什么的&#xff1f; 6.什么是高阶组件&#xff1f; 7.讲讲什么…...

Java高并发系列: 使用wait - notify实现高效异步方法

1. 背景 在项目开发中, 通常会有异步执行操作, 例如: 提交一个异步清空一系列数据库中ID ${_id} 的记录, 这个时候通常的做法是主线程将任务添加到一个异步队列中, 后台维护一个线程不断地循环扫描这个队列, 如果有需要执行的任务, 则执行相应的逻辑. 如下图所示: 2. 一个简…...

业务安全详解

文章目录 一、 业务安全概述1.1 业务安全现状1.1.1 业务逻辑漏洞1.1.2 黑客攻击的目标 二、 业务安全测试2.1 业务安全测试流程2.1.1 测试准备2.1.2 业务调研2.1.3 业务建模2.1.4 业务流程梳理2.1.5 业务风险点识别2.1.6 开展测试2.1.7 撰写报告 三、 业务安全经典场景3.1 业务…...

算法笔记--最大连续1的个数Ⅲ

leetcode题目链接:1004. 最大连续1的个数 III 题目描述 给定一个二进制数组 nums 和一个整数 k&#xff0c;如果可以翻转最多 k 个 0 &#xff0c;则返回 数组中连续 1 的最大个数 。 思路 这里可以转换思路&#xff0c;让题意更加明确:即&#xff0c;求一个最大连续区间…...

Linux CentOS7 添加中文输入法

在安装CentOS7时&#xff0c;现在默认安装了桌面中文系统。可以切换为英文&#xff0c;中英文可以按要求随时更换。而在CentOS7桌面环境下&#xff0c;显示中文非常方便、正确&#xff0c;但不能录入中文。 在远程登录系统的情况下&#xff0c;不论是系统语言&#xff08;LANG…...

Python接口自动化封装导出excel方法和读写excel数据

一、首先需要思考&#xff0c;我们在页面导出excel&#xff0c;用python导出如何写入文件的 封装前需要确认python导出excel接口返回的是一个什么样的数据类型 如下&#xff1a;我们先看下不对返回结果做处理&#xff0c;直接接收数据类型是一个对象&#xff0c;无法获取返回值…...

React三属性之:refs

作用 refs是为了获取节点,使用场景主要在需要操作dom的时候,比如echarts,就需要真实的dom节点 使用 import React from "react"; class RefsTest extends React.Component{state {value:输入框的值}refPlan React.createRef()logRef ()>{console.log(this.r…...

将Vue项目迁移到微信小程序中

文章目录 一、创建一个Vue.js的应用程序二、构建微信小程序1. 安装微信小程序构建工具2. 在vuejs项目的根目录中创建一个wepy.confgjs文件3. 在vuejs项目的根目录中运行构建 三、错误记录1. 找不到编译器&#xff1a;wepy-compiler-sass 一、创建一个Vue.js的应用程序 使用 Vu…...

php权限调整强制用户退出的解决方案

要强制用户重新登录&#xff0c;你可以采取以下步骤&#xff1a; 当用户登录时&#xff0c;将用户的登录状态和其他相关信息存储在服务器端。例如&#xff0c;你可以将用户ID、用户名或其他标识符存储在服务器的会话&#xff08;session&#xff09;中。当管理员修改用户的某些…...

[uniapp]踩坑日记 unexpected character > 1或‘=’>1 报错

在红色报错文档里下滑&#xff0c;找到Show more 根据提示看是缺少标签&#xff0c;如果不是缺少标签&#xff0c;看看view标签内容是否含有<、>、>、<号,把以上符合都进行以<号为例做{{“<”}}处理...

面试求职-经典面试问题

16个经典面试问题回答思路 面试过程中&#xff0c;面试官会向应聘者发问&#xff0c;而应聘者的回答将成为面试官考虑是否接受他的重要依据。对应聘者而言&#xff0c;了解这些问题背后的“猫腻”至关重要。本文对面试中经常出现的一些典型问题进行了整理&#xff0c;并给出相…...

在Linux服务器上部署Tornado项目

要在Linux服务器上部署Tornado项目&#xff0c;你可以按照以下步骤进行操作&#xff1a; 1、准备服务器&#xff1a; 确保你的服务器上安装了Python。Tornado通常与Python 3兼容&#xff0c;因此建议安装Python 3.x。 安装和配置一个Web服务器&#xff0c;如Nginx或Apache&a…...

怎么做网店网站/软件定制开发

容器因其轻量且可重用的代码&#xff0c;功能更灵活、开发成本更低而被得到越来越广泛的应用。Docker作为应用容器中最引人瞩目的实现方式&#xff0c;其优点很多&#xff0c;其诞生的目的就是便于持续集成和快速部署尽量减少中间环节&#xff0c;这也为其安全控制带来难度。容…...

云南建设厅网站首页/看b站视频下载软件

检测foodname是否重复 select foodname,count(*) as count from tablename group by foodname having count>1;...

做查询快递单号的网站多少钱/html网页制作网站

因为项目美观的需要&#xff0c;不想用默认的Tab控件&#xff0c;巨难看&#xff0c;找来找去。发现没有合适的&#xff0c;找了个老外的代码&#xff0c;改了下&#xff0c;自己实现了下&#xff0c;有用的童鞋&#xff0c;可以拿出用用&#xff0c;如果源代码更新&#xff0c…...

网上做问卷报酬不错的网站是/网络广告人社区

最近做了一个webservice应用,发现传输过程中,只要有特殊这符,就会导致出错 于是上网搜了一些方法,先把源WebService输出的内容进行特殊字符转换十六进制,接到收后再进行返转换 ///<summary>///StringExtenstion 的摘要说明///</summary>public static class String…...

深圳app开发公司哪家比较靠谱/西安关键词优化软件

计算(calc.cpp) 【问题描述】 小明在你的帮助下&#xff0c;破密了Ferrari设的密码门&#xff0c;正要往前走&#xff0c;突然又出现了一个密码门&#xff0c;门上有一个算式&#xff0c;其中只有“(”&#xff0c;“)”&#xff0c;“0-9”&#xff0c;“”&#xff0c;“-”&…...

上海网站推广汉狮/网站子域名查询

常用Action说明&#xff1a; String ADD_SHORTCUT_ACTION 动作&#xff1a;在系统中添加一个快捷方式。. “android.intent.action.ADD_SHORTCUT”String ALL_APPS_ACTION 动作&#xff1a;列举所有可用的应用。 输入&#xff1a;无。 “android.intent.action.ALL_APPS”Strin…...