vue學習筆記(慎入,內容僅供學習時查詢某些函式具體起到什麼作用)

我只是個小開發發表於2019-02-16

getAndRemoveAttr

從ast模板物件中取出相應的屬性。

  1. 檢測屬性是否存在,通過物件attrsMap來檢測,提升效率
  2. 如果存在,則從attrsList中中移除
  3. 如果第三個傳參為true,刪除attrsMap中對應的屬性
  4. 返回取到的結果,或者undefined
// note: this only removes the attr from the Array (attrsList) so that it
// doesn`t get processed by processAttrs.
// By default it does NOT remove it from the map (attrsMap) because the map is
// needed during codegen.
export function getAndRemoveAttr (
  el: ASTElement,
  name: string,
  removeFromMap?: boolean
): ?string {
  let val
  if ((val = el.attrsMap[name]) != null) {
    const list = el.attrsList
    for (let i = 0, l = list.length; i < l; i++) {
      if (list[i].name === name) {
        list.splice(i, 1)
        break
      }
    }
  }
  if (removeFromMap) {
    delete el.attrsMap[name]
  }
  return val
} 

getBindingAttr

獲取動態屬性值

  1. 拼接屬性名:|v-bind,呼叫getAndRemoveAttr讀取相應的屬性值
  2. 如果獲取到相應的屬性值,則呼叫parseFilters解析返回值中可能存在的過濾器,並返回
  3. 如果第三個傳參不為false,返回相應的靜態屬性,並將靜態屬性格式化為字串""demo1"",返回
  4. 否則,無返回
/** 第三個引數傳true會在獲取不到動態屬性的時候取靜態屬性 */
export function getBindingAttr (
  el: ASTElement,
  name: string,
  getStatic?: boolean
): ?string {
  const dynamicValue =
    getAndRemoveAttr(el, `:` + name) ||
    getAndRemoveAttr(el, `v-bind:` + name)
  if (dynamicValue != null) {
    // 如果存在過濾器,將匹配到的字串使用過濾器包裹。
    return parseFilters(dynamicValue)
  } else if (getStatic !== false) {
    const staticValue = getAndRemoveAttr(el, name)
    if (staticValue != null) {
      return JSON.stringify(staticValue)
    }
  }
} 

parseFilters

{{input | filter1 | filter2 }} 解析為:_f(“filter2”)(_f(“filter1”)(input))

/* @flow */

/** 匹配任意非空字元,),.,+,-,_,$,] 
* 排除一些其他用法產生的/,諸如a++ / b, a-- / b, a/b, (a + a1) / b, ../path
*/
const validDivisionCharRE = /[w).+-_$]]/

/** 解析出filter的條件是:匹配到|,
 * 並且|不在單引號,雙引號,模板引用符,正則,括號,中括號,大括號中,
 * 並且不是||
 */
export function parseFilters (exp: string): string {
  let inSingle = false
  let inDouble = false
  let inTemplateString = false
  let inRegex = false
  let curly = 0
  let square = 0
  let paren = 0
  let lastFilterIndex = 0
  let c, prev, i, expression, filters

  for (i = 0; i < exp.length; i++) {
    prev = c
    c = exp.charCodeAt(i)
    // 0x5C => 
    if (inSingle) {
      if (c === 0x27 && prev !== 0x5C) inSingle = false
    } else if (inDouble) {
      if (c === 0x22 && prev !== 0x5C) inDouble = false
    } else if (inTemplateString) {
      if (c === 0x60 && prev !== 0x5C) inTemplateString = false
    } else if (inRegex) {
      if (c === 0x2f && prev !== 0x5C) inRegex = false
    } else if (
      c === 0x7C && // |
      exp.charCodeAt(i + 1) !== 0x7C &&
      exp.charCodeAt(i - 1) !== 0x7C &&
      !curly && !square && !paren
    ) {
      if (expression === undefined) {
        // first filter, end of expression
        lastFilterIndex = i + 1
        expression = exp.slice(0, i).trim()
      } else {
        pushFilter()
      }
    } else {
      switch (c) {
        case 0x22: inDouble = true; break         // "
        case 0x27: inSingle = true; break         // `
        case 0x60: inTemplateString = true; break // `
        case 0x28: paren++; break                 // (
        case 0x29: paren--; break                 // )
        case 0x5B: square++; break                // [
        case 0x5D: square--; break                // ]
        case 0x7B: curly++; break                 // {
        case 0x7D: curly--; break                 // }
      }
      if (c === 0x2f) { // /
        let j = i - 1
        let p
        // find first non-whitespace prev char
        for (; j >= 0; j--) {
          p = exp.charAt(j)
          if (p !== ` `) break
        }
        if (!p || !validDivisionCharRE.test(p)) {
          inRegex = true
        }
      }
    }
  }

  if (expression === undefined) {
    expression = exp.slice(0, i).trim()
  } else if (lastFilterIndex !== 0) {
    pushFilter()
  }

  function pushFilter () {
    (filters || (filters = [])).push(exp.slice(lastFilterIndex, i).trim())
    lastFilterIndex = i + 1
  }

  if (filters) {
    for (i = 0; i < filters.length; i++) {
      expression = wrapFilter(expression, filters[i])
    }
  }
  return expression
}

function wrapFilter (exp: string, filter: string): string {
  const i = filter.indexOf(`(`)
  if (i < 0) {
    // _f: resolveFilter
    return `_f("${filter}")(${exp})`
  } else {
    const name = filter.slice(0, i)
    const args = filter.slice(i + 1)
    return `_f("${name}")(${exp}${args !== `)` ? `,` + args : args}`
  }
}
 

preTransformNode

/* @flow */

/**
 * Expand input[v-model] with dyanmic type bindings into v-if-else chains
 * Turn this:
 *   <input v-model="data[type]" :type="type">
 * into this:
 *   <input v-if="type === `checkbox`" type="checkbox" v-model="data[type]">
 *   <input v-else-if="type === `radio`" type="radio" v-model="data[type]">
 *   <input v-else :type="type" v-model="data[type]">
 */

import {
  addRawAttr,
  getBindingAttr,
  getAndRemoveAttr
} from `compiler/helpers`

import {
  processFor,
  processElement,
  addIfCondition,
  createASTElement
} from `compiler/parser/index`

/** 處理input標籤的v-model */
function preTransformNode (el: ASTElement, options: CompilerOptions) {
  if (el.tag === `input`) {
    const map = el.attrsMap
    if (!map[`v-model`]) {
      return
    }

    let typeBinding
    if (map[`:type`] || map[`v-bind:type`]) {
      typeBinding = getBindingAttr(el, `type`)
    }
    if (!map.type && !typeBinding && map[`v-bind`]) {
      typeBinding = `(${map[`v-bind`]}).type`
    }

    if (typeBinding) {
      const ifCondition = getAndRemoveAttr(el, `v-if`, true)
      const ifConditionExtra = ifCondition ? `&&(${ifCondition})` : ``
      const hasElse = getAndRemoveAttr(el, `v-else`, true) != null
      const elseIfCondition = getAndRemoveAttr(el, `v-else-if`, true)
      // 1. checkbox
      const branch0 = cloneASTElement(el)
      // process for on the main node,如果有v-for,將v-for解析為for,alias等屬性,並新增到branch0上
      processFor(branch0)
      // ast直接新增type屬性
      addRawAttr(branch0, `type`, `checkbox`)
      processElement(branch0, options)
      branch0.processed = true // prevent it from double-processed
      branch0.if = `(${typeBinding})===`checkbox`` + ifConditionExtra
      addIfCondition(branch0, {
        exp: branch0.if,
        block: branch0
      })
      // 2. add radio else-if condition
      const branch1 = cloneASTElement(el)
      getAndRemoveAttr(branch1, `v-for`, true)
      addRawAttr(branch1, `type`, `radio`)
      processElement(branch1, options)
      addIfCondition(branch0, {
        exp: `(${typeBinding})===`radio`` + ifConditionExtra,
        block: branch1
      })
      // 3. other
      const branch2 = cloneASTElement(el)
      getAndRemoveAttr(branch2, `v-for`, true)
      addRawAttr(branch2, `:type`, typeBinding)
      processElement(branch2, options)
      addIfCondition(branch0, {
        exp: ifCondition,
        block: branch2
      })

      if (hasElse) {
        branch0.else = true
      } else if (elseIfCondition) {
        branch0.elseif = elseIfCondition
      }
      return branch0
    }
  }
}

function cloneASTElement (el) {
  return createASTElement(el.tag, el.attrsList.slice(), el.parent)
}

export default {
  preTransformNode
}

processFor

extend(el, res),el.for, el.alias, el.iterator1, el.iterator2

export function processFor (el: ASTElement) {
  let exp
  if ((exp = getAndRemoveAttr(el, `v-for`))) {
    const res = parseFor(exp)
    if (res) {
      extend(el, res)
    } else if (process.env.NODE_ENV !== `production`) {
      warn(
        `Invalid v-for expression: ${exp}`
      )
    }
  }
} 

parseFor

這個函式解析v-for字串,並返回,比如
item in items返回{ for: items, alias: item };
(item, index) in items返回{ for: items, alias: item, iterator1: index };
(item, key, index) in items返回{ for: items, alias: item, iterator1: key, iterator2: index };

/**
 * item in items *?最小貪婪匹配,如 a in b in c 則匹配
 * a 而不是 a in b, in item 則匹配 ``
 */
export const forAliasRE = /([^]*?)s+(?:in|of)s+([^]*)/ 
/**
 * 匹配多個引數。
 */
export const forIteratorRE = /,([^,}]]*)(?:,([^,}]]*))?$/
const stripParensRE = /^(|)$/g

/** 解析v-for表示式,並返回 */
export function parseFor (exp: string): ?ForParseResult {
  const inMatch = exp.match(forAliasRE)
  if (!inMatch) return
  const res = {}
  res.for = inMatch[2].trim()
  const alias = inMatch[1].trim().replace(stripParensRE, ``)
  const iteratorMatch = alias.match(forIteratorRE)
  if (iteratorMatch) {
    res.alias = alias.replace(forIteratorRE, ``)
    res.iterator1 = iteratorMatch[1].trim()
    if (iteratorMatch[2]) {
      res.iterator2 = iteratorMatch[2].trim()
    }
  } else {
    res.alias = alias
  }
  return res
} 

processElement

export function processElement (element: ASTElement, options: CompilerOptions) {
  processKey(element)

  // determine whether this is a plain element after
  // removing structural attributes
  element.plain = !element.key && !element.attrsList.length

  processRef(element)
  processSlot(element)
  processComponent(element)
  /**
   * 賦值 el.staticClass, classBinding, staticStyle, styleBinding
   */
  for (let i = 0; i < transforms.length; i++) {
    element = transforms[i](element, options) || element
  }
  /** 處理attrsList 剩餘屬性 */
  processAttrs(element)
} 

processSlot

  1. 如果el.tag === slot,獲取el的name並賦值給slotname屬性
  2. 不是<slot>,獲取slot-scope,並給元素賦值slotScope屬性
  3. 獲取元素的動態slot,如果不是template且slotScope屬性不存在,則給el的attrs陣列屬性增加{name: `slot`, value: slotTarget}
function processSlot (el) {
  if (el.tag === `slot`) {
    el.slotName = getBindingAttr(el, `name`)
    if (process.env.NODE_ENV !== `production` && el.key) {
      warn(
        ``key` does not work on <slot> because slots are abstract outlets ` +
        `and can possibly expand into multiple elements. ` +
        `Use the key on a wrapping element instead.`
      )
    }
  } else {
    let slotScope
    if (el.tag === `template`) {
      slotScope = getAndRemoveAttr(el, `scope`)
      /* istanbul ignore if */
      if (process.env.NODE_ENV !== `production` && slotScope) {
        warn(
          `the "scope" attribute for scoped slots have been deprecated and ` +
          `replaced by "slot-scope" since 2.5. The new "slot-scope" attribute ` +
          `can also be used on plain elements in addition to <template> to ` +
          `denote scoped slots.`,
          true
        )
      }
      el.slotScope = slotScope || getAndRemoveAttr(el, `slot-scope`)
    } else if ((slotScope = getAndRemoveAttr(el, `slot-scope`))) {
      /* istanbul ignore if */
      if (process.env.NODE_ENV !== `production` && el.attrsMap[`v-for`]) {
        warn(
          `Ambiguous combined usage of slot-scope and v-for on <${el.tag}> ` +
          `(v-for takes higher priority). Use a wrapper <template> for the ` +
          `scoped slot to make it clearer.`,
          true
        )
      }
      el.slotScope = slotScope
    }
    const slotTarget = getBindingAttr(el, `slot`)
    if (slotTarget) {
      el.slotTarget = slotTarget === `""` ? `"default"` : slotTarget
      // preserve slot as an attribute for native shadow DOM compat
      // only for non-scoped slots.
      if (el.tag !== `template` && !el.slotScope) {
        addAttr(el, `slot`, slotTarget)
      }
    }
  }
} 

processComponent

function processComponent (el) {
  let binding
  if ((binding = getBindingAttr(el, `is`))) {
    el.component = binding
  }
  if (getAndRemoveAttr(el, `inline-template`) != null) {
    el.inlineTemplate = true
  }
} 

processAttrs

  1. 對於動態繫結屬性,首先擷取修飾符,並根據修飾符修飾name,增加事件等,再根據屬性名來判斷是繫結props還是attrs,需要動態更新的繫結props。
  2. 對於普通屬性,直接增加attr,muted除外,因為這個屬性如果使用attr無法觸發更新
function processAttrs (el) {
  const list = el.attrsList
  let i, l, name, rawName, value, modifiers, isProp
  for (i = 0, l = list.length; i < l; i++) {
    name = rawName = list[i].name
    value = list[i].value
    if (dirRE.test(name)) {
      // mark element as dynamic
      el.hasBindings = true
      // modifiers :input.number
      modifiers = parseModifiers(name)
      if (modifiers) {
        name = name.replace(modifierRE, ``) // :input
      }
      if (bindRE.test(name)) { // v-bind
        name = name.replace(bindRE, ``) // input
        value = parseFilters(value) // _f(`filter1`)(value)
        isProp = false
        if (modifiers) {
          if (modifiers.prop) {
            isProp = true
            name = camelize(name)
            if (name === `innerHtml`) name = `innerHTML`
          }
          if (modifiers.camel) {
            name = camelize(name)
          }
          // 雙向繫結,通過emit事件來觸發
          if (modifiers.sync) {
            /** 在el的event或者nativeevent中新增事件
             * el.event.value = {value: value=$event} || [...]
             */
            addHandler(
              el,
              `update:${camelize(name)}`,
              genAssignmentCode(value, `$event`) // `value=$event`
            )
          }
        }
        if (isProp || (
          !el.component && platformMustUseProp(el.tag, el.attrsMap.type, name)
        )) {
          addProp(el, name, value)
        } else {
          addAttr(el, name, value)
        }
      } else if (onRE.test(name)) { // v-on
        name = name.replace(onRE, ``)
        addHandler(el, name, value, modifiers, false, warn)
      } else { // normal directives
        name = name.replace(dirRE, ``)
        // parse arg
        const argMatch = name.match(argRE)
        const arg = argMatch && argMatch[1]
        if (arg) {
          name = name.slice(0, -(arg.length + 1))
        }
        addDirective(el, name, rawName, value, arg, modifiers)
        if (process.env.NODE_ENV !== `production` && name === `model`) {
          checkForAliasModel(el, value)
        }
      }
    } else {
      // literal attribute
      if (process.env.NODE_ENV !== `production`) {
        const res = parseText(value, delimiters)
        if (res) {
          warn(
            `${name}="${value}": ` +
            `Interpolation inside attributes has been removed. ` +
            `Use v-bind or the colon shorthand instead. For example, ` +
            `instead of <div id="{{ val }}">, use <div :id="val">.`
          )
        }
      }
      addAttr(el, name, JSON.stringify(value))
      // #6887 firefox doesn`t update muted state if set via attribute
      // even immediately after element creation
      if (!el.component &&
          name === `muted` &&
          platformMustUseProp(el.tag, el.attrsMap.type, name)) {
        addProp(el, name, `true`)
      }
    }
  }
}
 

parseModel

/**
 * Parse a v-model expression into a base path and a final key segment.
 * Handles both dot-path and possible square brackets.
 *
 * Possible cases:
 *
 * - test {exp: "test", key: null}
 * - test[key] {exp: "test", key: "key"}
 * - test[test1[key]] {exp: "test", key: "test1[key]"}
 * - test["a"][key] {exp: "test["a"]", key: "key"}
 * - xxx.test[a[a].test1[key]] {exp: "xxx.test", key: "a[a].test1[key]"}
 * - test.xxx.a["asa"][test1[key]] {exp: "test.xxx.a["asa"]", key: "test1[key]"}
 *
 */

let len, str, chr, index, expressionPos, expressionEndPos

type ModelParseResult = {
  exp: string,
  key: string | null
}

/** 獲取最靠後的一個完整項為key */ 
export function parseModel (val: string): ModelParseResult {
  // Fix https://github.com/vuejs/vue/pull/7730
  // allow v-model="obj.val " (trailing whitespace)
  val = val.trim()
  len = val.length

  if (val.indexOf(`[`) < 0 || val.lastIndexOf(`]`) < len - 1) {
    index = val.lastIndexOf(`.`)
    if (index > -1) {
      return {
        exp: val.slice(0, index),
        key: `"` + val.slice(index + 1) + `"`
      }
    } else {
      return {
        exp: val,
        key: null
      }
    }
  }

  str = val
  index = expressionPos = expressionEndPos = 0

  while (!eof()) {
    chr = next()
    /* istanbul ignore if */
    if (isStringStart(chr)) {
      parseString(chr)
    } else if (chr === 0x5B) { // [
      parseBracket(chr)
    }
  }

  return {
    exp: val.slice(0, expressionPos),
    key: val.slice(expressionPos + 1, expressionEndPos)
  }
}

function next (): number {
  return str.charCodeAt(++index)
}

function eof (): boolean {
  return index >= len
}

function isStringStart (chr: number): boolean {
  return chr === 0x22 || chr === 0x27
}

function parseBracket (chr: number): void {
  let inBracket = 1
  expressionPos = index
  while (!eof()) {
    chr = next()
    if (isStringStart(chr)) {
      parseString(chr)
      continue
    }
    if (chr === 0x5B) inBracket++
    if (chr === 0x5D) inBracket--
    if (inBracket === 0) {
      expressionEndPos = index
      break
    }
  }
}

function parseString (chr: number): void {
  const stringQuote = chr
  while (!eof()) {
    chr = next()
    if (chr === stringQuote) {
      break
    }
  }
}
 

model

根據不同的tag型別,將v-model轉化成不同的事件繫結onchange、oninput等

export default function model (
  el: ASTElement,
  dir: ASTDirective,
  _warn: Function
): ?boolean {
  warn = _warn
  const value = dir.value
  const modifiers = dir.modifiers
  const tag = el.tag
  const type = el.attrsMap.type

  if (process.env.NODE_ENV !== `production`) {
    // inputs with type="file" are read only and setting the input`s
    // value will throw an error.
    if (tag === `input` && type === `file`) {
      warn(
        `<${el.tag} v-model="${value}" type="file">:
` +
        `File inputs are read only. Use a v-on:change listener instead.`
      )
    }
  }
  if (el.component) {
    genComponentModel(el, value, modifiers)
    // component v-model doesn`t need extra runtime
    return false
  } else if (tag === `select`) {
    genSelect(el, value, modifiers)
  } else if (tag === `input` && type === `checkbox`) {
    genCheckboxModel(el, value, modifiers)
  } else if (tag === `input` && type === `radio`) {
    genRadioModel(el, value, modifiers)
  } else if (tag === `input` || tag === `textarea`) {
    genDefaultModel(el, value, modifiers)
  } else if (!config.isReservedTag(tag)) {
    genComponentModel(el, value, modifiers)
    // component v-model doesn`t need extra runtime
    return false
  } else if (process.env.NODE_ENV !== `production`) {
    warn(
      `<${el.tag} v-model="${value}">: ` +
      `v-model is not supported on this element type. ` +
      `If you are working with contenteditable, it`s recommended to ` +
      `wrap a library dedicated for that purpose inside a custom component.`
    )
  }

  // ensure runtime directive metadata
  return true
}

genComponentModel

返回設定value值的一個物件集合

export function genComponentModel (
  el: ASTElement,
  value: string,
  modifiers: ?ASTModifiers
): ?boolean {
  const { number, trim } = modifiers || {}

  const baseValueExpression = `$$v`
  let valueExpression = baseValueExpression
  if (trim) {
    valueExpression =
      `(typeof ${baseValueExpression} === `string`` +
      `? ${baseValueExpression}.trim()` +
      `: ${baseValueExpression})`
  }
  if (number) {
    valueExpression = `_n(${valueExpression})`
  }
  const assignment = genAssignmentCode(value, valueExpression)

  el.model = {
    value: `(${value})`,
    expression: `"${value}"`,
    callback: `function (${baseValueExpression}) {${assignment}}`  // 類似 function ($$v) { data=_n($$v.tirm()) }
  }
} 

genAssignmentCode

/**
 * Cross-platform codegen helper for generating v-model value assignment code.
 * 返回類似 data=_n($$v.tirm());
 * $set(data, key, _n($$v.tirm()));
 */
 
export function genAssignmentCode (
  value: string,
  assignment: string
): string {
  const res = parseModel(value)
  if (res.key === null) {
    return `${value}=${assignment}`
  } else {
    return `$set(${res.exp}, ${res.key}, ${assignment})`
  }
} 

addHandler

  1. 首先檢測modifiers引數是否存在,並根據引數中特定屬性去重寫name引數
  2. 再根據是否有native修飾符來決定向el.nativeEvents還是el.events新增對應名稱的屬性值{value:
    value.trim()}
export function addHandler (
  el: ASTElement,
  name: string,
  value: string,
  modifiers: ?ASTModifiers,
  important?: boolean,
  warn?: Function
) {
  modifiers = modifiers || emptyObject
  // warn prevent and passive modifier
  /* istanbul ignore if */
  if (
    process.env.NODE_ENV !== `production` && warn &&
    modifiers.prevent && modifiers.passive
  ) {
    warn(
      `passive and prevent can`t be used together. ` +
      `Passive handler can`t prevent default event.`
    )
  }

  // check capture modifier
  if (modifiers.capture) {
    delete modifiers.capture
    name = `!` + name // mark the event as captured
  }
  if (modifiers.once) {
    delete modifiers.once
    name = `~` + name // mark the event as once
  }
  /* istanbul ignore if */
  if (modifiers.passive) {
    delete modifiers.passive
    name = `&` + name // mark the event as passive
  }

  // normalize click.right and click.middle since they don`t actually fire
  // this is technically browser-specific, but at least for now browsers are
  // the only target envs that have right/middle clicks.
  if (name === `click`) {
    if (modifiers.right) {
      name = `contextmenu`
      delete modifiers.right
    } else if (modifiers.middle) {
      name = `mouseup`
    }
  }

  let events
  if (modifiers.native) {
    delete modifiers.native
    events = el.nativeEvents || (el.nativeEvents = {})
  } else {
    events = el.events || (el.events = {})
  }

  const newHandler: any = {
    value: value.trim()
  }
  if (modifiers !== emptyObject) {
    newHandler.modifiers = modifiers
  }

  const handlers = events[name]
  /* istanbul ignore if */
  if (Array.isArray(handlers)) {
    important ? handlers.unshift(newHandler) : handlers.push(newHandler)
  } else if (handlers) {
    events[name] = important ? [newHandler, handlers] : [handlers, newHandler]
  } else {
    events[name] = newHandler
  }

  el.plain = false
}

Array.apply(null, { length: 20 })

建立可遍歷的空陣列

相關文章