国产 无码 综合区,色欲AV无码国产永久播放,无码天堂亚洲国产AV,国产日韩欧美女同一区二区

【Vue2.0源碼學(xué)習(xí)】生命周期篇-初始化階段(initState)

這篇具有很好參考價值的文章主要介紹了【Vue2.0源碼學(xué)習(xí)】生命周期篇-初始化階段(initState)。希望對大家有所幫助。如果存在錯誤或未考慮完全的地方,請大家不吝賜教,您也可以點擊"舉報違法"按鈕提交疑問。

1. 前言

本篇文章介紹生命周期初始化階段所調(diào)用的第五個初始化函數(shù)——initState。 從函數(shù)名字上來看,這個函數(shù)是用來初始化實例狀態(tài)的,那么什么是實例的狀態(tài)呢?在前面文章中我們略有提及,在我們?nèi)粘i_發(fā)中,在Vue組件中會寫一些如props、data、methods、computedwatch選項,我們把這些選項稱為實例的狀態(tài)選項。也就是說,initState函數(shù)就是用來初始化這些狀態(tài)的,那么接下來我們就來分析該函數(shù)是如何初始化這些狀態(tài)選項的。

2. initState函數(shù)分析

首先我們先來分析initState函數(shù),該函數(shù)的定義位于源碼的src/core/instance/state.js中,如下:

export function initState (vm: Component) {
  vm._watchers = []
  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)
  }
}

可以看到,該函數(shù)的代碼并不多,而且邏輯也非常清晰。

首先,給實例上新增了一個屬性_watchers,用來存儲當前實例中所有的watcher實例,無論是使用vm.$watch注冊的watcher實例還是使用watch選項注冊的watcher實例,都會被保存到該屬性中。

這里我們再額外多說一點,在變化偵測篇中我們介紹了Vue中對數(shù)據(jù)變化的偵測是使用屬性攔截的方式實現(xiàn)的,但是Vue并不是對所有數(shù)據(jù)都使用屬性攔截的方式偵測變化,這是因為數(shù)據(jù)越多,數(shù)據(jù)上所綁定的依賴就會多,從而造成依賴追蹤的內(nèi)存開銷就會很大,所以從Vue 2.0版本起,Vue不再對所有數(shù)據(jù)都進行偵測,而是將偵測粒度提高到了組件層面,對每個組件進行偵測,所以在每個組件上新增了vm._watchers屬性,用來存放這個組件內(nèi)用到的所有狀態(tài)的依賴,當其中一個狀態(tài)發(fā)生變化時,就會通知到組件,然后由組件內(nèi)部使用虛擬DOM進行數(shù)據(jù)比對,從而降低內(nèi)存開銷,提高性能。

繼續(xù)回到源碼,接下來就是判斷實例中有哪些選項就調(diào)用對應(yīng)的選項初始化子函數(shù)進行初始化,如下:

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選項,如果有,就調(diào)用props選項初始化函數(shù)initProps去初始化props選項;

再判斷實例中是否有methods選項,如果有,就調(diào)用methods選項初始化函數(shù)initMethods去初始化methods選項;

接著再判斷實例中是否有data選項,如果有,就調(diào)用data選項初始化函數(shù)initData去初始化data選項;如果沒有,就把data當作空對象并將其轉(zhuǎn)換成響應(yīng)式;

接著再判斷實例中是否有computed選項,如果有,就調(diào)用computed選項初始化函數(shù)initComputed去初始化computed選項;

最后判斷實例中是否有watch選項,如果有,就調(diào)用watch選項初始化函數(shù)initWatch去初始化watch選項;

總之一句話就是:有什么選項就調(diào)用對應(yīng)的選項初始化子函數(shù)去初始化什么選項。

以上就是initState函數(shù)的所有邏輯,其實你會發(fā)現(xiàn),在函數(shù)內(nèi)部初始化這5個選項的時候它的順序是有意安排的,不是毫無章法的。如果你在開發(fā)中有注意到我們在data中可以使用props,在watch中可以觀察dataprops,之所以可以這樣做,就是因為在初始化的時候遵循了這種順序,先初始化props,接著初始化data,最后初始化watch。

下面我們就針對這5個狀態(tài)選項對應(yīng)的5個初始化子函數(shù)進行逐一分析,看看其內(nèi)部分別都是如何進行初始化的。

3. 初始化props

props選項通常是由當前組件的父級組件傳入的,當父組件在調(diào)用子組件的時候,通常會把props屬性值作為標簽屬性添加在子組件的標簽上,如下:

<Child prop1="xxx" prop2="yyy"></Child>

在前面文章介紹初始化事件initEvents函數(shù)的時候我們說了,在模板編譯的時候,當解析到組件標簽時會將所有的標簽屬性都解析出來然后在子組件實例化的時候傳給子組件,當然這里面就包括props數(shù)據(jù)。

在子組件內(nèi)部,通過props選項來接收父組件傳來的數(shù)據(jù),在接收的時候可以這樣寫:

// 寫法一
props: ['name']

// 寫法二
props: {
    name: String, // [String, Number]
}

// 寫法三
props: {
    name:{
		type: String
    }
}

可以看到,Vue給用戶提供的props選項寫法非常自由,根據(jù)Vue的慣例,寫法雖多但是最終處理的時候肯定只處理一種寫法,此時你肯定會想到,處理之前先對數(shù)據(jù)進行規(guī)范化,將所有寫法都轉(zhuǎn)化成一種寫法。對,你沒有猜錯,同規(guī)范化事件一樣,在合并屬性的時候也進行了props數(shù)據(jù)的規(guī)范化。

3.1 規(guī)范化數(shù)據(jù)

props數(shù)據(jù)規(guī)范化函數(shù)的定義位于源碼的src/core/util/options.js中,如下:

function normalizeProps (options, vm) {
  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選項,如果不存在,則直接返回。

const props = options.props
if (!props) return

如果存在,則定義一個空對象res,用來存儲最終的結(jié)果。接著判斷如果props選項是一個數(shù)組(寫法一),則遍歷該數(shù)組中的每一項元素,如果該元素是字符串,那么先將該元素統(tǒng)一轉(zhuǎn)化成駝峰式命名,然后將該元素作為key,將{type: null}作為value存入res中;如果該元素不是字符串,則拋出異常。如下:

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.')
        }
    }
}

如果props選項不是數(shù)組那就繼續(xù)判斷是不是一個對象,如果是一個對象,那就遍歷對象中的每一對鍵值,拿到每一對鍵值后,先將鍵名統(tǒng)一轉(zhuǎn)化成駝峰式命名,然后判斷值是否還是一個對象,如果值是對象(寫法三),那么就將該鍵值對存入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選項既不是數(shù)組也不是對象,那么如果在非生產(chǎn)環(huán)境下就拋出異常,最后將res作為規(guī)范化后的結(jié)果重新賦值給實例的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數(shù)據(jù)的規(guī)范化處理,可以看到,無論是三種寫法的哪一種,最終都會被轉(zhuǎn)化成如下寫法:

props: {
    name:{
        type: xxx
    }
}

3.2 initProps函數(shù)分析

props選項規(guī)范化完成之后,接下來我們就可以來真正的初始化props選項了,initProps函數(shù)的定義位于源碼的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 converted
  if (!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)
}

可以看到,該函數(shù)接收兩個參數(shù):當前Vue實例和當前實例規(guī)范化后的props選項。

在函數(shù)內(nèi)部首先定義了4個變量,分別是:

const propsData = vm.$options.propsData || {}
const props = vm._props = {}
const keys = vm.$options._propKeys = []
const isRoot = !vm.$parent
  • propsData:父組件傳入的真實props數(shù)據(jù)。
  • props:指向vm._props的指針,所有設(shè)置到props變量中的屬性都會保存到vm._props中。
  • keys:指向vm.$options._propKeys的指針,緩存props對象中的key,將來更新props時只需遍歷vm.$options._propKeys數(shù)組即可得到所有propskey。
  • isRoot:當前組件是否為根組件。

接著,判斷當前組件是否為根組件,如果不是,那么不需要將props數(shù)組轉(zhuǎn)換為響應(yīng)式的,toggleObserving(false)用來控制是否將數(shù)據(jù)轉(zhuǎn)換成響應(yīng)式。如下:

if (!isRoot) {
    toggleObserving(false)
}

接著,遍歷props選項拿到每一對鍵值,先將鍵名添加到keys中,然后調(diào)用validateProp函數(shù)(關(guān)于該函數(shù)下面會介紹)校驗父組件傳入的props數(shù)據(jù)類型是否匹配并獲取到傳入的值value,然后將鍵和值通過defineReactive函數(shù)添加到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中是否存在,如果不存在,則調(diào)用proxy函數(shù)在vm上設(shè)置一個以key為屬性的代碼,當使用vm[key]訪問數(shù)據(jù)時,其實訪問的是vm._props[key]。如下:

if (!(key in vm)) {
    proxy(vm, `_props`, key)
}

以上就是initProps函數(shù)的所有邏輯,接下來我們再看一下是如何通過validateProp函數(shù)校驗父組件傳入的props數(shù)據(jù)類型是否匹配并獲取到傳入的值的。

3.3 validateProp函數(shù)分析

validateProp函數(shù)的定義位于源碼的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 casting
  const 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 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') {
    assertProp(prop, key, value, vm, absent)
  }
  return value
}

可以看到,該函數(shù)接收4個參數(shù),分別是:

  • key:遍歷propOptions時拿到的每個屬性名。
  • propOptions:當前實例規(guī)范化后的props選項。
  • propsData:父組件傳入的真實props數(shù)據(jù)。
  • vm:當前實例。

在函數(shù)內(nèi)部首先定義了3個變量,分別是:

const prop = propOptions[key]
const absent = !hasOwn(propsData, key)
let value = propsData[key]
  • prop:當前keypropOptions中對應(yīng)的值。
  • absent:當前key是否在propsData中存在,即父組件是否傳入了該屬性。
  • value:當前keypropsData中對應(yīng)的值,即父組件對于該屬性傳入的真實值。

接著,判斷proptype屬性是否是布爾類型(Boolean),getTypeIndex函數(shù)用于判斷proptype屬性中是否存在某種類型,如果存在,則返回該類型在type屬性中的索引(因為type屬性可以是數(shù)組),如果不存在則返回-1。

如果是布爾類型的話,那么有兩種邊界情況需要單獨處理:

  1. 如果absenttrue,即父組件沒有傳入該prop屬性并且該屬性也沒有默認值的時候,將該屬性值設(shè)置為false,如下:

    if (absent && !hasOwn(prop, 'default')) {
        value = false
    }
    
  2. 如果父組件傳入了該prop屬性,那么需要滿足以下幾點:

    • 該屬性值為空字符串或者屬性值與屬性名相等;
    • proptype屬性中不存在String類型;
    • 如果proptype屬性中存在String類型,那么Boolean類型在type屬性中的索引必須小于String類型的索引,即Boolean類型的優(yōu)先級更高;

    則將該屬性值設(shè)置為true,如下:

    if (value === '' || value === hyphenate(key)) {
        const stringIndex = getTypeIndex(String, prop.type)
        if (stringIndex < 0 || booleanIndex < stringIndex) {
            value = true
        }
    }
    

    另外,在判斷屬性值與屬性名相等的時候,是先將屬性名由駝峰式轉(zhuǎn)換成用-連接的字符串,下面的這幾種寫法,子組件的prop都將被設(shè)置為true

    <Child name></Child>
    <Child name="name"></Child>
    <Child userName="user-name"></Child>
    

    如果不是布爾類型,是其它類型的話,那就只需判斷父組件是否傳入該屬性即可,如果沒有傳入,則該屬性值為undefined,此時調(diào)用getPropDefaultValue函數(shù)(關(guān)于該函數(shù)下面會介紹)獲取該屬性的默認值,并將其轉(zhuǎn)換成響應(yīng)式,如下:

    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)
    }
    

    如果父組件傳入了該屬性并且也有對應(yīng)的真實值,那么在非生產(chǎn)環(huán)境下會調(diào)用assertProp函數(shù)(關(guān)于該函數(shù)下面會介紹)校驗該屬性值是否與要求的類型相匹配。如下:

    if (process.env.NODE_ENV !== 'production' ) {
        assertProp(prop, key, value, vm, absent)
    }
    

    最后將父組件傳入的該屬性的真實值返回。

3.4 getPropDefaultValue函數(shù)分析

getPropDefaultValue函數(shù)的定義位于源碼的src/core/util/props.js中,如下:

function getPropDefaultValue (vm, prop, key){
  // 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
}

該函數(shù)接收三個參數(shù),分別是:

  • vm:當前實例;
  • prop:子組件props選項中的每個key對應(yīng)的值;
  • key:子組件props選項中的每個key

其作用是根據(jù)子組件props選項中的key獲取其對應(yīng)的默認值。

首先判斷prop中是否有default屬性,如果沒有,則表示沒有默認值,直接返回。如下:

if (!hasOwn(prop, 'default')) {
    return undefined
}

如果有則取出default屬性,賦給變量def。接著判斷在非生產(chǎn)環(huán)境下def是否是一個對象,如果是,則拋出警告:對象或數(shù)組默認值必須從一個工廠函數(shù)獲取。如下:

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是否為函數(shù)并且prop.type不為Function,如果是的話表明def是一個返回對象或數(shù)組的工廠函數(shù),那么將函數(shù)的返回值作為默認值返回;如果def不是函數(shù),那么則將def作為默認值返回。如下:

return typeof def === 'function' && getType(prop.type) !== 'Function'
    ? def.call(vm)
	: def

3.5 assertProp函數(shù)分析

assertProp函數(shù)的定義位于源碼的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.type
  let valid = !type || type === true
  const 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.validator
  if (validator) {
    if (!validator(value)) {
      warn(
        'Invalid prop: custom validator check failed for prop "' + name + '".',
        vm
      )
    }
  }
}

該函數(shù)接收5個參數(shù),分別是:

  • prop:prop選項;
  • name:propsprop選項的key;
  • value:父組件傳入的propsDatakey對應(yīng)的真實數(shù)據(jù);
  • vm:當前實例;
  • absent:當前key是否在propsData中存在,即父組件是否傳入了該屬性。

其作用是校驗父組件傳來的真實值是否與proptype類型相匹配,如果不匹配則在非生產(chǎn)環(huán)境下拋出警告。

函數(shù)內(nèi)部首先判斷prop中如果設(shè)置了必填項(即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:保存期望類型的數(shù)組,當校驗失敗拋出警告時,會提示用戶該屬性所期望的類型是什么;

通常情況下,type可以是一個原生構(gòu)造函數(shù),也可以是一個包含多種類型的數(shù)組,還可以不設(shè)置該屬性。如果用戶設(shè)置的是原生構(gòu)造函數(shù)或數(shù)組,那么此時vaild默認為false!type),如果用戶沒有設(shè)置該屬性,表示不需要校驗,那么此時vaild默認為true,即校驗成功。

另外,當type等于true時,即出現(xiàn)這樣的寫法:props:{name:true},這說明prop一定會校驗成功。所以當出現(xiàn)這種語法的時候,此時type === true,所以vaild默認為true。

接下來開始校驗類型,如果用戶設(shè)置了type屬性,則判斷該屬性是不是數(shù)組,如果不是,則統(tǒng)一轉(zhuǎn)化為數(shù)組,方便后續(xù)處理,如下:

if (type) {
    if (!Array.isArray(type)) {
        type = [type]
    }
}

接下來遍歷type數(shù)組,并調(diào)用assertType函數(shù)校驗value。assertType函數(shù)校驗后會返回一個對象,如下:

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

然后將被校驗的類型添加到expectedTypes中,并將vaild變量設(shè)置為assertedType.valid,如下:

for (let i = 0; i < type.length && !valid; i++) {
    const assertedType = assertType(value, type[i])
    expectedTypes.push(assertedType.expectedType || '')
    valid = assertedType.valid
}

這里請注意:上面循環(huán)中的條件語句有這樣一個條件:!vaild,即type數(shù)組中還要有一個校驗成功,循環(huán)立即結(jié)束,表示校驗通過。

接下來,如果循環(huán)完畢后vaildfalse,即表示校驗未通過,則拋出警告。如下:

if (!valid) {
    warn(
        `Invalid prop: type check failed for prop "${name}".` +
        ` Expected ${expectedTypes.map(capitalize).join(', ')}` +
        `, got ${toRawType(value)}.`,
        vm
    )
    return
}

另外,prop選項還支持自定義校驗函數(shù),如下:

props:{
   // 自定義驗證函數(shù)
    propF: {
      validator: function (value) {
        // 這個值必須匹配下列字符串中的一個
        return ['success', 'warning', 'danger'].indexOf(value) !== -1
      }
    }
}

所以還需要使用用戶傳入的自定義校驗函數(shù)來校驗數(shù)據(jù)。首先獲取到用戶傳入的校驗函數(shù),調(diào)用該函數(shù)并將待校驗的數(shù)據(jù)傳入,如果校驗失敗,則拋出警告。如下:

const validator = prop.validator
if (validator) {
    if (!validator(value)) {
        warn(
            'Invalid prop: custom validator check failed for prop "' + name + '".',
            vm
        )
    }
}

4. 初始化methods

初始化methods相較而言就比較簡單了,它的初始化函數(shù)定義位于源碼的src/core/instance/state.js中,如下:

function initMethods (vm, methods) {
  const props = vm.$options.props
  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
        )
      }
      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的命名符不符合命名規(guī)范?如果method既有又符合規(guī)范那就把它掛載到vm實例上。下面我們就逐行分析源碼,來過一遍這三件事。

首先,遍歷methods選項中的每一個對象,在非生產(chǎn)環(huán)境下判斷如果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中某個屬性名重復(fù)了,就拋出異常:提示用戶方法名重復(fù)了。如下:

if (props && hasOwn(props, key)) {
    warn(
        `Method "${key}" has already been defined as a prop.`,
        vm
    )
}

接著判斷如果methods中某個方法名如果在實例vm中已經(jīng)存在并且方法名是以_$開頭的,就拋出異常:提示用戶方法名命名不規(guī)范。如下:

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函數(shù)是用來判斷字符串是否以_$開頭。

最后,如果上述判斷都沒問題,那就method綁定到實例vm上,這樣,我們就可以通過this.xxx來訪問methods選項中的xxx方法了,如下:

vm[key] = methods[key] == null ? noop : bind(methods[key], vm)

5. 初始化data

初始化data也比較簡單,它的初始化函數(shù)定義位于源碼的src/core/instance/state.js中,如下:

function initData (vm) {
    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, `_data`, key)
        }
    }
    // observe data
    observe(data, true /* asRootData */)
}

可以看到,initData函數(shù)的邏輯并不復(fù)雜,跟initMethods函數(shù)的邏輯有幾分相似。就是通過一系列條件判斷用戶傳入的data選項是否合法,最后將data轉(zhuǎn)換成響應(yīng)式并綁定到實例vm上。下面我們就來仔細看一下代碼邏輯。

首先獲取到用戶傳入的data選項,賦給變量data,同時將變量data作為指針指向vm._data,然后判斷data是不是一個函數(shù),如果是就調(diào)用getData函數(shù)獲取其返回值,將其保存到vm._data中。如果不是,就將其本身保存到vm._data中。如下:

let data = vm.$options.data
data = vm._data = typeof data === 'function'
    ? getData(data, vm)
	: data || {}

我們知道,無論傳入的data選項是不是一個函數(shù),它最終的值都應(yīng)該是一個對象,如果不是對象的話,就拋出警告:提示用戶data應(yīng)該是一個對象。如下:

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對象中的每一項,在非生產(chǎn)環(huán)境下判斷data對象中是否存在某一項的keymethods中某個屬性名重復(fù),如果存在重復(fù),就拋出警告:提示用戶屬性名重復(fù)。如下:

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

接著再判斷是否存在某一項的keyprop中某個屬性名重復(fù),如果存在重復(fù),就拋出警告:提示用戶屬性名重復(fù)。如下:

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
    )
}

如果都沒有重復(fù),則調(diào)用proxy函數(shù)將data對象中key不以_$開頭的屬性代理到實例vm上,這樣,我們就可以通過this.xxx來訪問data選項中的xxx數(shù)據(jù)了。如下:

if (!isReserved(key)) {
    proxy(vm, `_data`, key)
}

最后,調(diào)用observe函數(shù)將data中的數(shù)據(jù)轉(zhuǎn)化成響應(yīng)式,如下:

observe(data, true /* asRootData */)

6. 初始化computed

計算屬性computed相信大家一定不會陌生,在日常開發(fā)中肯定會經(jīng)常用到,而且我們知道計算屬性有一個很大的特點就是: 計算屬性的結(jié)果會被緩存,除非依賴的響應(yīng)式屬性變化才會重新計算。 那么接下來我們就來看一下計算屬性是如何實現(xiàn)這些功能的的。

6.1 回顧用法

首先,根據(jù)官方文檔的使用示例,我們來回顧一下計算屬性的用法,如下:

var vm = new Vue({
  data: { a: 1 },
  computed: {
    // 僅讀取
    aDouble: function () {
      return this.a * 2
    },
    // 讀取和設(shè)置
    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選項中的屬性值可以是一個函數(shù),那么該函數(shù)默認為取值器getter,用于僅讀取數(shù)據(jù);還可以是一個對象,對象里面有取值器getter和存值器setter,用于讀取和設(shè)置數(shù)據(jù)。

6.2 initComputed函數(shù)分析

了解了計算屬性的用法之后,下面我們就來分析一下計算屬性的初始化函數(shù)initComputed的內(nèi)部原理是怎樣的。initComputed函數(shù)的定義位于源碼的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.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
            )
        }

        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)
            }
        }
    }
}

可以看到,在函數(shù)內(nèi)部,首先定義了一個變量watchers并將其賦值為空對象,同時將其作為指針指向vm._computedWatchers,如下:

const watchers = vm._computedWatchers = Object.create(null)

接著,遍歷computed選項中的每一項屬性,首先獲取到每一項的屬性值,記作userDef,然后判斷userDef是不是一個函數(shù),如果是函數(shù),則該函數(shù)默認為取值器getter,將其賦值給變量getter;如果不是函數(shù),則說明是一個對象,則取對象中的get屬性作為取值器賦給變量getter。如下:

const userDef = computed[key]
const getter = typeof userDef === 'function' ? userDef : userDef.get

接著判斷在非生產(chǎn)環(huán)境下如果上面兩種情況取到的取值器不存在,則拋出警告:提示用戶計算屬性必須有取值器。如下:

if (process.env.NODE_ENV !== 'production' && getter == null) {
    warn(
        `Getter is missing for computed property "${key}".`,
        vm
    )
}

接著判斷如果不是在服務(wù)端渲染環(huán)境下,則創(chuàng)建一個watcher實例,并將當前循環(huán)到的的屬性名作為鍵,創(chuàng)建的watcher實例作為值存入watchers對象中。如下:

if (!isSSR) {
    // create internal watcher for the computed property.
    watchers[key] = new Watcher(
        vm,
        getter || noop,
        noop,
        computedWatcherOptions
    )
}

最后,判斷當前循環(huán)到的的屬性名是否存在于當前實例vm上,如果存在,則在非生產(chǎn)環(huán)境下拋出警告;如果不存在,則調(diào)用defineComputed函數(shù)為實例vm上設(shè)置計算屬性。

以上就是initComputed函數(shù)的內(nèi)部邏輯,接下里我們再來看一下defineComputed函數(shù)是如何為實例vm上設(shè)置計算屬性的。

6.3 defineComputed函數(shù)分析

defineComputed函數(shù)的定義位于源碼的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)
      : userDef
    sharedPropertyDefinition.set = noop
  } else {
    sharedPropertyDefinition.get = userDef.get
      ? shouldCache && userDef.cache !== false
        ? createComputedGetter(key)
        : userDef.get
      : noop
    sharedPropertyDefinition.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)
}

該函數(shù)接受3個參數(shù),分別是:targetkeyuserDef。其作用是為target上定義一個屬性key,并且屬性keygettersetter根據(jù)userDef的值來設(shè)置。下面我們就來看一下該函數(shù)的具體邏輯。

首先定義了變量sharedPropertyDefinition,它是一個默認的屬性描述符。

接著,在函數(shù)內(nèi)部定義了變量shouldCache,用于標識計算屬性是否應(yīng)該有緩存。該變量的值是當前環(huán)境是否為非服務(wù)端渲染環(huán)境,如果是非服務(wù)端渲染環(huán)境則該變量為true。也就是說,只有在非服務(wù)端渲染環(huán)境下計算屬性才應(yīng)該有緩存。如下:

const shouldCache = !isServerRendering()

接著,判斷如果userDef是一個函數(shù),則該函數(shù)默認為取值器getter,此處在非服務(wù)端渲染環(huán)境下并沒有直接使用userDef作為getter,而是調(diào)用createComputedGetter函數(shù)(關(guān)于該函數(shù)下面會介紹)創(chuàng)建了一個getter,這是因為userDef只是一個普通的getter,它并沒有緩存功能,所以我們需要額外創(chuàng)建一個具有緩存功能的getter,而在服務(wù)端渲染環(huán)境下可以直接使用userDef作為getter,因為在服務(wù)端渲染環(huán)境下計算屬性不需要緩存。由于用戶沒有設(shè)置setter函數(shù),所以將sharedPropertyDefinition.set設(shè)置為noop。如下:

if (typeof userDef === 'function') {
    sharedPropertyDefinition.get = shouldCache
        ? createComputedGetter(key)
    : userDef
    sharedPropertyDefinition.set = noop
}

如果userDef不是一個函數(shù),那么就將它當作對象處理。在設(shè)置sharedPropertyDefinition.get的時候先判斷userDef.get是否存在,如果不存在,則將其設(shè)置為noop,如果存在,則同上面一樣,在非服務(wù)端渲染環(huán)境下并且用戶沒有明確的將userDef.cache設(shè)置為false時調(diào)用createComputedGetter函數(shù)創(chuàng)建一個getter賦給sharedPropertyDefinition.get。然后設(shè)置sharedPropertyDefinition.setuserDef.set函數(shù)。如下:

sharedPropertyDefinition.get = userDef.get
      ? shouldCache && userDef.cache !== false
        ? createComputedGetter(key)
        : userDef.get
      : noop
sharedPropertyDefinition.set = userDef.set
    ? userDef.set
	: noop

接著,再判斷在非生產(chǎn)環(huán)境下如果用戶沒有設(shè)置setter的話,那么就給setter一個默認函數(shù),這是為了防止用戶在沒有設(shè)置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
        )
    }
}

最后調(diào)用Object.defineProperty方法將屬性key綁定到target上,其中的屬性描述符就是上面設(shè)置的sharedPropertyDefinition。如此以來,就將計算屬性綁定到實例vm上了。

以上就是defineComputed函數(shù)的所有邏輯。另外,我們發(fā)現(xiàn),計算屬性有沒有緩存及其響應(yīng)式貌似主要在于是否將getter設(shè)置為createComputedGetter函數(shù)的返回結(jié)果。那么接下來,我們就對這個createComputedGetter函數(shù)一探究竟。

6.4 createComputedGetter函數(shù)分析

createComputedGetter函數(shù)的定義位于源碼的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()
        }
    }
}

可以看到,該函數(shù)是一個高階函數(shù),其內(nèi)部返回了一個computedGetter函數(shù),所以其實是將computedGetter函數(shù)賦給了sharedPropertyDefinition.get。當獲取計算屬性的值時會執(zhí)行屬性的getter,而屬性的getter就是 sharedPropertyDefinition.get,也就是說最終執(zhí)行的 computedGetter函數(shù)。

computedGetter函數(shù)內(nèi)部,首先存儲在當前實例上_computedWatchers屬性中key所對應(yīng)的watcher實例,如果watcher存在,則調(diào)用watcher實例上的depend方法和evaluate方法,并且將evaluate方法的返回值作為計算屬性的計算結(jié)果返回。那么watcher實例上的depend方法和evaluate方法又是什么呢?

6.5 depend和evaluate

回顧上文中創(chuàng)建watcher實例的時候:

const computedWatcherOptions = { computed: true }
watchers[key] = new Watcher(
    vm,
    getter || noop,
    noop,
    computedWatcherOptions
)

傳入的參數(shù)中第二個參數(shù)是getter函數(shù),第四個參數(shù)是一個對象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 watchers
        if (typeof expOrFn === 'function') {
            this.getter = expOrFn
        }

        if (this.computed) {
            this.value = undefined
            this.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 value
            const oldValue = this.value
            this.value = value
            this.dirty = false
            if (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類的時候,第四個參數(shù)傳入了一個對象computedWatcherOptions = { computed: true },該對象中的computed屬性標志著這個watcher實例是計算屬性的watcher實例,即Watcher類中的this.computed屬性,同時類中還定義了this.dirty屬性用于標志計算屬性的返回值是否有變化,計算屬性的緩存就是通過這個屬性來判斷的,每當計算屬性依賴的數(shù)據(jù)發(fā)生變化時,會將this.dirty屬性設(shè)置為true,這樣下一次讀取計算屬性時,會重新計算結(jié)果返回,否則直接返回之前的計算結(jié)果。

當調(diào)用watcher.depend()方法時,會將讀取計算屬性的那個watcher添加到計算屬性的watcher實例的依賴列表中,當計算屬性中用到的數(shù)據(jù)發(fā)生變化時,計算屬性的watcher實例就會執(zhí)行watcher.update()方法,在update方法中會判斷當前的watcher是不是計算屬性的watcher,如果是則調(diào)用getAndInvoke去對比計算屬性的返回值是否發(fā)生了變化,如果真的發(fā)生變化,則執(zhí)行回調(diào),通知那些讀取計算屬性的watcher重新執(zhí)行渲染邏輯。

當調(diào)用watcher.evaluate()方法時,會先判斷this.dirty是否為true,如果為true,則表明計算屬性所依賴的數(shù)據(jù)發(fā)生了變化,則調(diào)用this.get()重新獲取計算結(jié)果最后返回;如果為false,則直接返回之前的計算結(jié)果。

其內(nèi)部原理如圖所示:

【Vue2.0源碼學(xué)習(xí)】生命周期篇-初始化階段(initState),Vue深入學(xué)習(xí),學(xué)習(xí),前端,vue.js

7. 初始化watch

接下來就是最后一個初始化函數(shù)了——初始化watch選項。在日常開發(fā)中watch選項也經(jīng)常會使用到,它可以用來偵聽某個已有的數(shù)據(jù),當該數(shù)據(jù)發(fā)生變化時執(zhí)行對應(yīng)的回調(diào)函數(shù)。那么,接下來我們就來看一些watch選項是如何被初始化的。

7.1 回顧用法

首先,根據(jù)官方文檔的使用示例,我們來回顧一下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',
    // 深度偵聽,該回調(diào)會在任何被偵聽的對象的 property 改變時被調(diào)用,不論其被嵌套多深
    c: {
      handler: function (val, oldVal) { /* ... */ },
      deep: true
    },
    // 該回調(diào)將會在偵聽開始之后被立即調(diào)用
    d: {
      handler: 'someMethod',
      immediate: true
    },
    // 調(diào)用多個回調(diào)
    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選項是一個對象,鍵是需要觀察的表達式,值是對應(yīng)回調(diào)函數(shù)。值也可以是方法名,或者包含選項的對象。既然給用戶提供的用法靈活,那么在代碼中就需要按條件來判斷,根據(jù)不同的用法做相應(yīng)的處理。

7.2 initWatch函數(shù)分析

了解了watch選項的用法之后,下面我們就來分析一下watch選項的初始化函數(shù)initWatch的內(nèi)部原理是怎樣的。initWatch函數(shù)的定義位于源碼的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)
    }
  }
}

可以看到,在函數(shù)內(nèi)部會遍歷watch選項,拿到每一項的key和對應(yīng)的值handler。然后判斷handler是否為數(shù)組,如果是數(shù)組則循環(huán)該數(shù)組并將數(shù)組中的每一項依次調(diào)用createWatcher函數(shù)來創(chuàng)建watcher;如果不是數(shù)組,則直接調(diào)用createWatcher函數(shù)來創(chuàng)建watcher。那么這個createWatcher函數(shù)是如何創(chuàng)建watcher的呢?

7.3 createWatcher函數(shù)分析

createWatcher函數(shù)的定義位于源碼的src/core/instance/state.js中,如下:

function createWatcher (
  vm: Component,
  expOrFn: string | Function,
  handler: any,
  options?: Object
) {
  if (isPlainObject(handler)) {
    options = handler
    handler = handler.handler
  }
  if (typeof handler === 'string') {
    handler = vm[handler]
  }
  return vm.$watch(expOrFn, handler, options)
}

可以看到,該函數(shù)接收4個參數(shù),分別是:

  • vm:當前實例;
  • expOrFn:被偵聽的屬性表達式
  • handler:watch選項中每一項的值
  • options:用于傳遞給vm.$watch的選項對象

在該函數(shù)內(nèi)部,首先會判斷傳入的handler是否為一個對象,如果是一個對象,那么就認為用戶使用的是這種寫法:

watch: {
    c: {
        handler: function (val, oldVal) { /* ... */ },
		deep: true
    }
}

即帶有偵聽選項的寫法,此時就將handler對象整體記作options,把handler對象中的handler屬性作為真正的回調(diào)函數(shù)記作handler,如下:

if (isPlainObject(handler)) {
    options = handler
    handler = handler.handler
}

接著判斷傳入的handler是否為一個字符串,如果是一個字符串,那么就認為用戶使用的是這種寫法:

watch: {
    // methods選項中的方法名
    b: 'someMethod',
}

即回調(diào)函數(shù)是methods選項中的一個方法名,我們知道,在初始化methods選項的時候會將選項中的每一個方法都綁定到當前實例上,所以此時我們只需從當前實例上取出該方法作為真正的回調(diào)函數(shù)記作handler,如下:

if (typeof handler === 'string') {
    handler = vm[handler]
}

如果既不是對象又不是字符串,那么我們就認為它是一個函數(shù),就不做任何處理。

針對不同類型的值處理完畢后,expOrFn是被偵聽的屬性表達式,handler變量是回調(diào)函數(shù),options變量為偵聽選項,最后,調(diào)用vm.$watcher方法(關(guān)于該方法在介紹全局實例方法的時候會詳細介紹)并傳入以上三個參數(shù)完成初始化watch

8. 總結(jié)

本篇文章介紹了生命周期初始化階段所調(diào)用的第五個初始化函數(shù)——initState。該初始化函數(shù)內(nèi)部總共初始化了5個選項,分別是:props、methodsdata、computedwatch。

這5個選項的初始化順序不是任意的,而是經(jīng)過精心安排的。只有按照這種順序初始化我們才能在開發(fā)中在data中可以使用props,在watch中可以觀察dataprops。

這5個選項中的所有屬性最終都會被綁定到實例上,這也就是我們?yōu)槭裁纯梢允褂?code>this.xxx來訪問任意屬性。同時正是因為這一點,這5個選項中的所有屬性名都不應(yīng)該有所重復(fù),這樣會造成屬性之間相互覆蓋。

最后,我們對這5個選項分別都是如何進行初始化的內(nèi)部原理進行了逐一分析。文章來源地址http://www.zghlxwxcb.cn/news/detail-706166.html

到了這里,關(guān)于【Vue2.0源碼學(xué)習(xí)】生命周期篇-初始化階段(initState)的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!

本文來自互聯(lián)網(wǎng)用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務(wù),不擁有所有權(quán),不承擔相關(guān)法律責(zé)任。如若轉(zhuǎn)載,請注明出處: 如若內(nèi)容造成侵權(quán)/違法違規(guī)/事實不符,請點擊違法舉報進行投訴反饋,一經(jīng)查實,立即刪除!

領(lǐng)支付寶紅包贊助服務(wù)器費用

相關(guān)文章

  • vue2的生命周期詳解(代碼演示+源碼)

    vue2的生命周期詳解(代碼演示+源碼)

    ? ? ? ? 生命周期是指從開始創(chuàng)建、初始化數(shù)據(jù)、編譯模版、掛載 Dom - 渲染、更新 - 渲染、卸載等一系列過程,我們稱這是 Vue 的生命周期,它主要強調(diào)一個時間段。用一句話來概括就是: Vue實例的生命周期: 從創(chuàng)建到銷毀的整個過程 Vue框架內(nèi)置函數(shù),隨著組件的生命周期階

    2024年02月04日
    瀏覽(13)
  • 【Spring Boot 源碼學(xué)習(xí)】ConditionEvaluationReport 日志記錄上下文初始化器

    【Spring Boot 源碼學(xué)習(xí)】ConditionEvaluationReport 日志記錄上下文初始化器

    《Spring Boot 源碼學(xué)習(xí)系列》 上篇博文《共享 MetadataReaderFactory 上下文初始化器》, Huazie 帶大家詳細分析了 SharedMetadataReaderFactoryContextInitializer 。而在 spring-boot-autoconfigure 子模塊中預(yù)置的上下文初始化器中,除了共享 MetadataReaderFactory 上下文初始化器,還有一個尚未分析。 那么

    2024年04月13日
    瀏覽(67)
  • 【SA8295P 源碼分析】86 - AIS Camera Device 設(shè)備初始化 之 AisProcChainManager 模塊初始化源碼分析

    【源碼分析】 因為一些原因,本文需要移除, 對于已經(jīng)購買的兄弟,不用擔心,不是跑路, 我會繼續(xù)持續(xù)提供技術(shù)支持, 有什么模塊想學(xué)習(xí)的,或者有什么問題有疑問的, 請私聊我,我們 +VX 溝通技術(shù)問題,一起學(xué)習(xí),一起進步 接下來,我一一私聊已經(jīng)購買的兄弟添加V

    2024年02月10日
    瀏覽(25)
  • SpringMVC源碼解析——DispatcherServlet初始化

    SpringMVC源碼解析——DispatcherServlet初始化

    在Spring中,ContextLoaderListener只是輔助功能,用于創(chuàng)建WebApplicationContext類型的實例,而真正的邏輯實現(xiàn)其實是在DispatcherServlet中進行的,DispatcherServlet是實現(xiàn)Servlet接口的實現(xiàn)類。Servlet是一個JAVA編寫的程序,此程序是基于HTTP協(xié)議的,在服務(wù)端運行的(如Tomcat),是按照Servlet規(guī)范

    2024年02月03日
    瀏覽(23)
  • canal server初始化源碼分析

    canal server初始化源碼分析

    在開始之前,我們可以先了解下, canal 配置方式 ManagerCanalInstanceGenerator: 基于manager管理的配置方式,實時感知配置并進行server重啟 SpringCanalInstanceGenerator:基于本地spring xml的配置方式,對于多instance的時候,不便于擴展,一個instance一個xml配置 canal 配置文件 canal.properties ?

    2024年01月19日
    瀏覽(27)
  • vue中初始化

    主要是掛載一些全局方法 響應(yīng)數(shù)據(jù)相關(guān)的Vue.set, Vue.delete, Vue.nextTick以及Vue.observable 插件相關(guān)的Vue.use 對象合并相關(guān)Vue.mixin 類繼承相關(guān)的Vue.extend 資源相關(guān),如組件,過濾器,自定義指令Vue.component, Vue.filter, Vue.directive 配置相關(guān)Vue.config以及Vue.options中的components,filters,directives 定

    2023年04月22日
    瀏覽(38)
  • 深度學(xué)習(xí)參數(shù)初始化(二)Kaiming初始化 含代碼

    深度學(xué)習(xí)參數(shù)初始化(二)Kaiming初始化 含代碼

    目錄 一、介紹 二、基礎(chǔ)知識 三、Kaiming初始化的假設(shè)條件? 四、Kaiming初始化的簡單的公式推導(dǎo) 1.前向傳播 2.反向傳播 五、Pytorch實現(xiàn) 深度學(xué)習(xí)參數(shù)初始化系列: (一)Xavier初始化 含代碼 (二)Kaiming初始化 含代碼 ????????Kaiming初始化論文地址:https://arxiv.org/abs/1502.01

    2024年02月04日
    瀏覽(19)
  • UE4 源碼解析----引擎初始化流程

    UE4 源碼解析----引擎初始化流程

    ? ?在研究UE4的源碼過程中著實不理解的地方有很多,今天給大家分享一下UE4引擎的初始化流程。 C++的函數(shù)入口都是Main() 函數(shù)入口,UE4也是一樣,EngineSourceRuntimeLaunchPrivate Windows函數(shù)入口 ?引擎入口函數(shù)為:GuardedMain UE4所有相關(guān)的代碼都在游戲循環(huán)函數(shù)中,在Launch.cpp中,寫

    2024年02月06日
    瀏覽(22)
  • Vue初始化項目加載邏輯

    Vue初始化項目加載邏輯

    項目創(chuàng)建 我們只需要創(chuàng)建項目即可,剩余的依賴都沒必要安裝 我們先來看main.js,咱們加了一行備注 通過備注可知,我們首先加載的是App.vue 我們再來看一下App.vue 里都有啥 也就是下面這個紅框里的內(nèi)容才是 那下面的內(nèi)容是哪里來的呢 那就需要看一下路由設(shè)置了 我們看到/目

    2024年02月08日
    瀏覽(24)
  • 【源碼解析】聊聊SpringBean是如何初始化和創(chuàng)建

    【源碼解析】聊聊SpringBean是如何初始化和創(chuàng)建

    我們知道通過類進行修復(fù)不同的屬性,比如單例、原型等,而具體的流程是怎么樣的呢,這一篇我們開始從源碼的視角分析以下。 在刷新容器中有一個方法,其實就是 Bean創(chuàng)建的過程。 而BeanFactory中 preInstantiateSingletons是初始化所有的bean對象的核心流程。 而這里通過去遍歷所

    2024年02月05日
    瀏覽(23)

覺得文章有用就打賞一下文章作者

支付寶掃一掃打賞

博客贊助

微信掃一掃打賞

請作者喝杯咖啡吧~博客贊助

支付寶掃一掃領(lǐng)取紅包,優(yōu)惠每天領(lǐng)

二維碼1

領(lǐng)取紅包

二維碼2

領(lǐng)紅包