vue2源码解析---v-model双向数据绑定

什么是v-model

v-model 是 Vue 中的一个指令,用于实现表单元素与 Vue 实例中数据的双向绑定。这意味着当表单元素的值发生变化时,Vue 实例中的数据也会随之更新

工作原理

生成ast树

本质上是语法糖 结合了v-bind和v-on两个指令

示例代码

javascript 复制代码
new Vue({
  el: '#app',
  data () {
    return {
      msg: 'Hello, msg'
    }
  },
  template: `<input v-model="msg" />`
})

源码解析

processElemen t方法中调用processAttrs来处理标签上面解析的各种属性

javascript 复制代码
export function processElement (
  element: ASTElement,
  options: CompilerOptions
) {
  // ...省略代码
  processAttrs(element)
  return element
}

进入processAttrs这个方法中 用于构建抽象的语法树

javascript 复制代码
export const dirRE = process.env.VBIND_PROP_SHORTHAND
  ? /^v-|^@|^:|^\.|^#/
  : /^v-|^@|^:|^#/
const argRE = /:(.*)$/
function processAttrs (el) {
  const list = el.attrsList
  let i, l, name, rawName, value, modifiers, syncGen, isDynamic
  for (i = 0, l = list.length; i < l; i++) {
    name = rawName = list[i].name
    value = list[i].value
    if (dirRE.test(name)) {
      el.hasBindings = true
      // modifiers省略代码
      if (bindRE.test(name)) {
        // v-bind省略代码
      } else if (onRE.test(name)) {
        // v-on省略代码
      } else {
        // normal directives
        name = name.replace(dirRE, '')
        // parse arg
        //先使用dirRE正则表达式把v-model字符串中的v-前缀去掉,
        //此时name的值就变成了model
        //它又使用了argRE正则表达式来匹配指令参数
        //示例 
       // const template = `<input v-model:value="msg" />`
       // 匹配到的指令参数
      //const arg = 'value'
        const argMatch = name.match(argRE)
        let arg = argMatch && argMatch[1]
        isDynamic = false
        if (arg) {
          name = name.slice(0, -(arg.length + 1))
          if (dynamicArgRE.test(arg)) {
            arg = arg.slice(1, -1)
            isDynamic = true
          }
        }
        addDirective(el, name, rawName, value, arg, isDynamic, modifiers, list[i])
        if (process.env.NODE_ENV !== 'production' && name === 'model') {
          checkForAliasModel(el, value)
        }
      }
    } else {
      // ...省略代码
    }
  }
}

处理完毕后进入调用addDirective方法,给ast对象添加directives属性

javascript 复制代码
export function addDirective (
  el: ASTElement,
  name: string,
  rawName: string,
  value: string,
  arg: ?string,
  isDynamicArg: boolean,
  modifiers: ?ASTModifiers,
  range?: Range
) {
  (el.directives || (el.directives = [])).push(rangeSetItem({
    name,
    rawName,
    value,
    arg,
    isDynamicArg,
    modifiers
  }, range))
  el.plain = false
}

生成的ast树如下所示

javascript 复制代码
const ast = {
  type: 1,
  tag: 'input',
  attrsList: [
    { name: 'v-model', value: 'msg' }
  ],
  attrsMap: {
    'v-model': 'msg'
  },
  directives: [
    { name: 'model', rawName: 'v-model', value: 'msg' }
  ]
}

codegen阶段

codegen代码生成阶段,会在genData方法中调用genDirectives来处理指令

javascript 复制代码
export function genData (el: ASTElement, state: CodegenState): string {
  let data = '{'
  const dirs = genDirectives(el, state)
  if (dirs) data += dirs + ','
  // ...省略代码
  return data
}

function genDirectives (el: ASTElement, state: CodegenState): string | void {
  const dirs = el.directives
  if (!dirs) return
  let res = 'directives:['
  let hasRuntime = false
  let i, l, dir, needRuntime
  for (i = 0, l = dirs.length; i < l; i++) {
    dir = dirs[i]
    needRuntime = true
    const gen: DirectiveFunction = state.directives[dir.name]
    if (gen) {
      // compile-time directive that manipulates AST.
      // returns true if it also needs a runtime counterpart.
      needRuntime = !!gen(el, dir, state.warn)
    }
    if (needRuntime) {
      hasRuntime = true
      res += `{name:"${dir.name}",rawName:"${dir.rawName}"${
        dir.value ? `,value:(${dir.value}),expression:${JSON.stringify(dir.value)}` : ''
      }${
        dir.arg ? `,arg:${dir.isDynamicArg ? dir.arg : `"${dir.arg}"`}` : ''
      }${
        dir.modifiers ? `,modifiers:${JSON.stringify(dir.modifiers)}` : ''
      }},`
    }
  }
  if (hasRuntime) {
    return res.slice(0, -1) + ']'
  }
}

与其他指令不同 state.directives,这个属性是在CodegenState类的构造函数中被处理的

javascript 复制代码
export class CodegenState {
  options: CompilerOptions;
  warn: Function;
  transforms: Array<TransformFunction>;
  dataGenFns: Array<DataGenFunction>;
  directives: { [key: string]: DirectiveFunction };
  maybeComponent: (el: ASTElement) => boolean;
  onceId: number;
  staticRenderFns: Array<string>;
  pre: boolean;

  constructor (options: CompilerOptions) {
    this.options = options
    // ...省略代码
    this.directives = extend(extend({}, baseDirectives), options.directives)
    // ...省略代码
  }
}

directives中 v-model中

javascript 复制代码
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">:\n` +
        `File inputs are read only. Use a v-on:change listener instead.`,
        el.rawAttrsMap['v-model']
      )
    }
  }

  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.',
      el.rawAttrsMap['v-model']
    )
  }

  // ensure runtime directive metadata
  return true
}

最后代码生成阶段

javascript 复制代码
function genDefaultModel (
  el: ASTElement,
  value: string,
  modifiers: ?ASTModifiers
): ?boolean {
  // ...省略代码
  addProp(el, 'value', `(${value})`)
  addHandler(el, event, code, null, true)
  // ...省略代码
}

● addProp:调用addProp是为了给ast添加一个value的props属性。

● addHandler:调用addHandler是为了给ast添加一个事件监听,至于到底监听什么事件取决于v-model作用于什么标签。

所以处理之后多了props和events属性

javascript 复制代码
export function genData (el: ASTElement, state: CodegenState): string {
  let data = '{'
  // directive
  const dirs = genDirectives(el, state)
  if (dirs) data += dirs + ','
  // ...省略代码
  // DOM props
  if (el.props) {
    data += `domProps:${genProps(el.props)},`
  }
  // event handlers
  if (el.events) {
    data += `${genHandlers(el.events, false)},`
  }
  // ...省略代码
  return data
}

总结

其实学源码学到后续感觉有点懵 然后就是复习js 感觉红宝书讲的挺多的 马上也要期末考试了 希望期末平稳度过。

相关推荐
Json____9 分钟前
学法减分交管12123模拟练习小程序源码前端和后端和搭建教程
前端·后端·学习·小程序·uni-app·学法减分·驾考题库
迂 幵18 分钟前
vue el-table 超出隐藏移入弹窗显示
javascript·vue.js·elementui
上趣工作室22 分钟前
vue2在el-dialog打开的时候使该el-dialog中的某个输入框获得焦点方法总结
前端·javascript·vue.js
家里有只小肥猫22 分钟前
el-tree 父节点隐藏
前端·javascript·vue.js
fkalis23 分钟前
【海外SRC漏洞挖掘】谷歌语法发现XSS+Waf Bypass
前端·xss
zxg_神说要有光1 小时前
自由职业第二年,我忘记了为什么出发
前端·javascript·程序员
陈随易1 小时前
农村程序员-关于小孩教育的思考
前端·后端·程序员
云深时现月1 小时前
jenkins使用cli发行uni-app到h5
前端·uni-app·jenkins
昨天今天明天好多天2 小时前
【Node.js]
前端·node.js
亿牛云爬虫专家2 小时前
Puppeteer教程:使用CSS选择器点击和爬取动态数据
javascript·css·爬虫·爬虫代理·puppeteer·代理ip