vue2.0源码分析

486 阅读4分钟

vue3.0已经出来一段时间了,目前整理一下vue2.0源码的相关知识点,准备着手向3.0的进军啦~

1.准备工作

  1. down源码:git clone git@github.com:vuejs/vue.git

  2. 下载rollup:npm i rollup -g

  3. 增加--sourcemap

    "dev": "rollup -w -c scripts/config.js --sourcemap --environment TARGET:web-full-dev",
    

   4. npm run dev命令会在dist目录下新增vue.jsvue.js.map

   5.新建一个测试的html文件,script引入dist下的vue.js文件

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="utf-8">
  <title>Vue.js modal component example</title>
  <script src="../../dist/vue.js"></script>
</head>

<body>
  <div id="app">
      <p>name</p>
      <p>{{name}}</p>
  </div>
  <script>
    const app = new Vue({
      el: '#app',
      data:{
          name: 'vue源码分析'
      }
    })
  </script>
</body>

</html>

    6.掌握断点使用方式

2.重要文件分析

步骤:根据以下代码寻找vue的入口文件,再一步步寻找vue的构造函数

"dev": "rollup -w -c scripts/config.js --sourcemap --environment TARGET:web-full-dev",

可知开始文件为scripts/config.js,参数为web-full-dev

摘取查找到的代码:

1.src/platforms/web/entry-runtime-with-compiler.js

主要作用:覆盖$mount,执行模板解析和编译,导出render函数

主要内容就在这个方法里

分析代码可知,最终目的是为了覆盖$mountreturn mount.call(this, el, hydrating)

在new vue中的优先级是render > template > el

在接着往上找Vue的构造函数

2.src/platforms/web/runtime/index.js

主要作用:该文件主要在Vue的原型上定义了__patch_和``定义$mount

在接着往上找Vue的构造函数

3.src/core/index.js

主要作用:定义全局api

在接着往上找Vue的构造函数

4.src/core/instance/index.js

vue的构造函数文件,只是简单的执行了一个init方法

查看initMixin方法

拓展:

定义实例方法:

stateMixin(Vue) //$set,$delete.$watch
eventsMixin(Vue) //$on,$emit...
lifecycleMixin(Vue) //_updata,$forceUpdate,$destroy...
renderMixin(Vue) //$nextTick,_update...

5.src/core/instance/init.js

主要作用是初始化init方法

重要代码如下:感兴趣的可以自己深入研究看看

vm._self = vm
initLifecycle(vm) //初始化组件相关的参数,$parent,$root等等。组件创建是自上而下,挂载是自下而上
initEvents(vm) //处理父组件传入的事件添加监听
initRender(vm) //声明$slots和$createElement()
callHook(vm, 'beforeCreate') //调用生命周期函数
initInjections(vm) // resolve injections before data/props 注入数据
initState(vm)  // ** 数据的初始化,响应式
initProvide(vm) // resolve provide after data/props 提供数据
callHook(vm, 'created') //调用生命周期函数

3.初始化过程分析

断点步骤:

右侧断点行可以看出初始化执行的步骤:

执行步骤是从下往上,可点击每一项查看每一个数据的变化,方便分析初始化的整个流程

断点6处可以看出来,2.0中一个组件只有一个watcher

整理以下:

new Vue() => this._init() => $mount => mountComponent => _render() => _update

this._init(): 初始化各种数据事件方法

mountComponent:声明updateComponent方法,创建组件的Watcher 

_render:获取虚拟dom(vnode)

_update: 通过__patch__将虚拟dom转换成真实dom

4.数据响应实现

<body>
  <div id="app">
      <p>name</p>
      <p>{{list[0]}}</p>
  </div>
  <script>
    const app = new Vue({
      el: '#app',
      data:{
        list:{
          name:'响应式',
          age: 18
        }
      }
    })
  </script>
</body>

分析入口:src/core/instance/init.js中的initState(vm)方法开始

在上面的重点文件讲解中提到过

断点进入initState(vm) => 断点进入initData(vm) => 断点进入observe(data, true /* asRootData */) => 进入重点文件src/core/observer/index.js

src/core/instance/state.js

主要作用:获取data去重,设置代理,启动响应式

1.observe相关

src/core/observer下都是数据响应相关

observe():主要是return ob,ob是一个观察者,负责通知和更新。如果接受的数据存ob直接返回,否则ob = new Observer(value)创建一个实例

Observer类:如果传入value是一个对象,所以设置完相关变量直接走this.walk(value)方法,循环遍历之后defineReactive(obj, keys[i])。拓展若是数组,则先该value的__proto__属性设置为Object.create(Array.prototype)。接着this.observeArray(value),目的是为了让数组里面的对象也实现响应式

defineReactive:new了一个依赖dep,所以一个key就有一个dep对象,如果接受的obj存在childOb就重复observe()方法,否则就可以走Object.defineProperty()中的get或者set方法

observe(data):总结递归遍历添加ob图:

当读取到data中的数据的时候,会触发Object.defineProperty()中的get方法,实现在拿到数据之前的数据劫持。get中主要是进行依赖收集dep.depend(),将正在使用该数据的组件进行收集,该组件我们也称为观察者。如果是修改数据,则触发set方法通知更新dep.notify()

更多的可以自己下去研究一下setset、delete

2.dep和watcher

观察者Watcher使用:
src/core/instance/lifecycle.js中的mountComponent方法中

dep和watcher是多对多的关系

因为不只是组件创建是实例的时候才有(mountComponent),当同一个key使用watch或者$watch的时候也会new Watcher(),感兴趣的可以src/core/instance/state.js中查看$watch方法

以下dep和watcher之间的关系:

注:以上各重要方法均为简写或改写只为精简表达

5.vue异步更新实现

需要了解浏览器事件循环机制:宏任务和微任务

宏任务:settimeout、setinterval、js线程、setImmediate等。浏览器完成一个宏任务,会在当前任务下的所以微任务全部完成之后,下一个宏任务开始之前对页面进行重新渲染

微任务:promise、MutaionObserver等。当前宏任务完成之后立即执行,直到当前宏任务下所以的微任务都完成之后,才开始下一个宏任务。

实现流程:

修改数据触发set() => dep.notify() => watcher.updata()  => watcher.run() => watcher.get() =>

 this.getter = expOrFn  =>vm._render() => vm._update() => vm.__patch__涉及diff算法

⚠️以上this.getter为vm._update(vm._render(), hydrating);

具体分析watcher.updata() :

update () {
  if (this.lazy) {
    this.dirty = true //if计算属性
  } else if (this.sync) {
    this.run() //if 同步更新
  } else {
    queueWatcher(this) //异步更新
  }
}

queueWatcher():只保留重点代码

export function queueWatcher (watcher: Watcher) {
  const id = watcher.id
  //去重,同一个watcher只添加一次到队列
  if (has[id] == null) {
    has[id] = true
    if (!flushing) {
      queue.push(watcher)
    }
    if (!waiting) {
      //异步刷新队列--微任务
      nextTick(flushSchedulerQueue)
    }
  }
}

//flushSchedulerQueue =>执行watcher.run()

nextTick:src/core/util/next-tick.js

nextTick(flushSchedulerQueue)将flushSchedulerQueue方法追加到callbacks数组中(入队等待执行)  => timerFunc()

判断兼容性,优雅降级:Promise > MutationObserver > setImmediate > setTimeout

6.虚拟dom和diff算法

上面说到流程vm._update() => vm.__patch__涉及diff算法,下面就详细探索一番

虚拟dom:vnode是通过vm._render()return出来的对象哟~

Vue.prototype._update = function (vnode: VNode, hydrating?: boolean) {
    const vm: Component = this
    const prevVnode = vm._vnode
    vm._vnode = vnode
    if (!prevVnode) {
	// initial render初始化
	vm.$el = vm.__patch__(vm.$el, vnode, hydrating, false /* removeOnly */)
    } else {
	// updates 更新
	vm.$el = vm.__patch__(prevVnode, vnode)
    }
}

src/core/vdom/patch.js,相关的diff算法都在该js文件

1.界面初始化-vm.__patch__流程

vm.$el = vm.__patch__(vm.$el, vnode, hydrating, false /* removeOnly */)

return function patch(oldVnode, vnode, hydrating, removeOnly) {
    let isInitialPatch = false
    const insertedVnodeQueue = []

    //如果传入的是真实节点,则是初始化,否则就是更新
    const isRealElement = isDef(oldVnode.nodeType)
    //初始化,创建新的dom,追加到body,删除宿主元素
    if (isRealElement) {
	oldVnode = emptyNodeAt(oldVnode)
    }
    const oldElm = oldVnode.elm
    const parentElm = nodeOps.parentNode(oldElm)
    //创建新的dom
    createElm(  
	vnode,
	insertedVnodeQueue,
	oldElm._leaveCb ? null : parentElm,
	nodeOps.nextSibling(oldElm)
    )
    //在此之前界面状态看下图
    //删除宿主元素
    if (isDef(parentElm)) {
	removeVnodes([oldVnode], 0, 0)
    } else if (isDef(oldVnode.tag)) {
	invokeDestroyHook(oldVnode)
    }
    invokeInsertHook(vnode, insertedVnodeQueue, isInitialPatch)
    return vnode.elm
}

以上,页面初始化的时候patch函数完成界面就完成更新啦~

注意⚠️:每一个知识模块都不是单独的无联系的,注意将流程结合起来,就不会迷茫了。。。

2.diff算法-界面更新vm.__patch__(prevVnode, vnode)

return function patch(oldVnode, vnode, hydrating, removeOnly) {
    let isInitialPatch = false
    const insertedVnodeQueue = []
    //如果传入的是真实节点,则是初始化,否则就是更新
    const isRealElement = isDef(oldVnode.nodeType)
    if (!isRealElement && sameVnode(oldVnode, vnode)) {
	//新旧都存在,执行diff算法,*****
	patchVnode(oldVnode, vnode, insertedVnodeQueue, null, null, removeOnly)
    }
    invokeInsertHook(vnode, insertedVnodeQueue, isInitialPatch)
    return vnode.elm
}

1.patchVnode

function patchVnode( oldVnode, vnode, insertedVnodeQueue, ownerArray, index, removeOnly ) {
  const oldCh = oldVnode.children
  const ch = vnode.children
  //属性更新
  if (isDef(data) && isPatchable(vnode)) {
    for (i = 0; i < cbs.update.length; ++i) cbs.update[i](oldVnode, vnode)
    if (isDef(i = data.hook) && isDef(i = i.update)) i(oldVnode, vnode)
  }
  //判断是否是元素
  if (isUndef(vnode.text)) {
    //双方都有孩子
    if (isDef(oldCh) && isDef(ch)) {
      //比孩子
      if (oldCh !== ch) updateChildren(elm, oldCh, ch, insertedVnodeQueue, removeOnly)
    } else if (isDef(ch)) {
      if (process.env.NODE_ENV !== 'production') {
        checkDuplicateKeys(ch)
      }
      //清空老节点文本
      if (isDef(oldVnode.text)) nodeOps.setTextContent(elm, '')
      //创建孩子并追加
      addVnodes(elm, null, ch, 0, ch.length - 1, insertedVnodeQueue)
    } else if (isDef(oldCh)) {
      //新节点没有孩子,老节点有直接删除
      removeVnodes(oldCh, 0, oldCh.length - 1)
    } else if (isDef(oldVnode.text)) {
      nodeOps.setTextContent(elm, '')
    }
  } else if (oldVnode.text !== vnode.text) {
    //双方都是文本,更新文本
    nodeOps.setTextContent(elm, vnode.text)
  }
}

总结:比较新旧vnode,根据情况会执行三种类型的操作:属性更新、节点更新、文本更新

  1. diff算法:如果新旧节点都有children,执行updateChildren
  2. 新节点有children,老节点没有,清空老节点文本,增加子节点
  3. 新节点没有children,老节点有,直接删除该节点下的所以子节点
  4. 都无,只是文本替换

2.updateChildren

算法思路:深度优先,同级比较

比较孩子的算法是比较复杂且消耗性能的一件工作,所以vue里面优化算法,提出了四种假设,如果不满足四种假设的情况下才暴力循环查找

四种假设优化为下图:oldStartVnode、oldEndVnode和newStartVnode、newEndVnode两两比较

function sameVnode (a, b) {
  return ( //是否存在key值
    a.key === b.key && ( //不存在key也为true,所以就地复用
      (
        a.tag === b.tag && //标签是否相同
        a.isComment === b.isComment && //都不是注释
        isDef(a.data) === isDef(b.data) && 
        sameInputType(a, b)  //input的类型是否一样
      ) || (
        isTrue(a.isAsyncPlaceholder) &&
        a.asyncFactory === b.asyncFactory &&
        isUndef(b.asyncFactory.error)
      )
    )
  )
}

这里的key使用是一个关键点,关于key的优点和就地复用等在diff算法中的作用可以参考文档

muyiy.cn/question/fr…中说明,再结合sameVnode就能明白。

举例分析:首首满足sameVnode

首首满足sameVnode情况下:将oldStartVnode和newStartVnode进行patchVnode,当节点下所以比较全部完成之后,在分别移动指针,进行下一轮的首位四种假设的比较。尾尾处理情况类似

首尾满足sameVnode

if (sameVnode(oldStartVnode, newEndVnode)) { // Vnode moved right
  patchVnode(oldStartVnode, newEndVnode, insertedVnodeQueue, newCh, newEndIdx)
  canMove && nodeOps.insertBefore(parentElm, oldStartVnode.elm, nodeOps.nextSibling(oldEndVnode.elm))
  oldStartVnode = oldCh[++oldStartIdx]
  newEndVnode = newCh[--newEndIdx]
}

首尾满足sameVnode情况下:将oldStartVnode和newEndVnode进行patchVnode,当节点下所以比较全部完成之后,需要将oldStartVnode位置移动到原本的oldEndVnode之后,再分别移动指针,进行下一轮的首位四种假设的比较。尾首情况类似

如果不满足假设的四种形式,则需要循环查找

if (isUndef(oldKeyToIdx)) oldKeyToIdx = createKeyToOldIdx(oldCh, oldStartIdx, oldEndIdx)
//查找在老得孩子数组中的索引
idxInOld = isDef(newStartVnode.key) ? oldKeyToIdx[newStartVnode.key] : findIdxInOld(newStartVnode, oldCh, oldStartIdx, oldEndIdx)
if (isUndef(idxInOld)) { // New element
  //没找到就创建
  createElm(newStartVnode, insertedVnodeQueue, parentElm, oldStartVnode.elm, false, newCh, newStartIdx)
} else {
  //找到
  vnodeToMove = oldCh[idxInOld]
  if (sameVnode(vnodeToMove, newStartVnode)) {
    patchVnode(vnodeToMove, newStartVnode, insertedVnodeQueue, newCh, newStartIdx)
    oldCh[idxInOld] = undefined
    canMove && nodeOps.insertBefore(parentElm, vnodeToMove.elm, oldStartVnode.elm)
  } else {
    // same key but different element. treat as new element
    createElm(newStartVnode, insertedVnodeQueue, parentElm, oldStartVnode.elm, false, newCh, newStartIdx)
  }
}
newStartVnode = newCh[++newStartIdx]

如果设置了key,用idxInOld表示在oldvnode中查找对应的newStartVnode.key索引,否则在oldvnode循环查找和newStartVnode满足sameVnode的节点索引

如果没有,就证明是新增加的,所以createElm

如果有进一步判断vnodeToMovenewStartVnode,是否满足sameVnode

满足就递归两节点下全部,全部完成之后,移动节点和指针,如上图所示

不满足则说明肯定是通过key方法找到的,但是key虽然相同,但是没有相比的价值,则需要创建新节点。侧面说明开发的时候不要用index等可能会重复的数据表示key哟~

循环♻️完毕

如果oldStartIdx > oldEndIdx ,oldvnode已经循环结束了,但是vnode还没有,所以有新添加的节点,将vnode中剩下的对应dom插入到真实dom

反正newStartIdx > newEndIdx, vnode已经循环结束了,但是oldvnode还没有,所以oldvnode中多余的节点直接删除

举例如下图:oldStartIdx > oldEndIdx

3.diff总结

1.什么是diff

diff是虚拟dom的必然产物,比较新旧dom的过程就是diff过程。

2.diff的必要性

vue2.x中一个组件就有一个watcher,相比1.x的版本中,watcher的力度变低。一个组件中data可能对应多个key,为了更好的定位变化的key,就需要引入diff

3.diff发生的地方

当数据变化需要更新视图之前,触发pathVnode()方法,开始diff算法

4.diff的高效性

提出常见的四种假设情况比较新旧虚拟dom,降低比较时间复杂度。如果都不满足,循环遍历查找节点是否存在。用户若设置了key值,通可以通过key值高效对比。

5.diff遵循的原则

深度优先,同层比较

7.部分常见问题的源码解析

1.列表渲染中key的作用

对列表渲染来说:

默认情况下,vue采用的是就地更新的策略。并不会去移动dom元素的位置。

使用key的情况下,会根据diff算法执行patchVnode函数,进行头尾四种比较,并更改元素的位置

重点是sameVnode

function sameVnode (a, b) {
  return ( //是否存在key值
    a.key === b.key && ( //不存在key也为true,所以就地复用
      (
        a.tag === b.tag && //标签是否相同
        a.isComment === b.isComment && //都不是注释
        isDef(a.data) === isDef(b.data) && 
        sameInputType(a, b)  //input的类型是否一样
      ) || (
        isTrue(a.isAsyncPlaceholder) &&
        a.asyncFactory === b.asyncFactory &&
        isUndef(b.asyncFactory.error)
      )
    )
  )
}

demo:

没有设置key的时候:就地更新原则,循环分别a变更为d,b变更c,c变更为b,d变更为a

设置key的时候: diff算法执行patchVnode,移动节点元素位置

注意⚠️:

如果针对特别简单的文本内容,且标签元素均相同,没有设置key,循环新旧节点对比时均满足sameVnode(oldStartVnode, newStartVnode)方法,也就是就地更新直接更改相关的文本内容。减少节点的创建或者销毁等等

但是如果设置了key,首尾比较不满的情况下,还需循环查找是否在别的位置,不存在才会创建并添加节点,这其中无疑会浪费时间。所以不区分场景就说key提高了性能,就是耍流氓

结论:

1.不用key;

  • 就地更新节点,减少节点的创建或者销毁,会有渲染性能上的提升
  • 无法维持组件的状态,不能实现过渡效果
  • 也有可能带来性能下降,针对复杂列表结构。节点顺序差别很大,就地复用的节点过多,导致创建或删除节点的数量过多,相比有key时性能下降

2.使用key

  • 维持组件组件状态

  • 元素查找性能提升

  • 尽可能多的复用相同的节点,性能会得到提升

可以仔细琢磨琢磨这些文字~

2.patch函数的获取

如果翻看之前的重要文件分析部分,不难发现src/platforms/web/runtime/index.js路径主要是指定了一个打补丁的方法,并创建$mount

import { patch } from './patch'
Vue.prototype.__patch__ = inBrowser ? patch : noop

Vue.prototype.$mount = function (
  el?: string | Element,
  hydrating?: boolean
): Component {
  el = el && inBrowser ? query(el) : undefined
  return mountComponent(this, el, hydrating)
}

上面patch方法主要是:

import * as nodeOps from 'web/runtime/node-ops'
import { createPatchFunction } from 'core/vdom/patch'
import baseModules from 'core/vdom/modules/index'
import  platformModules from 'web/runtime/modules/index'

const modules = platformModules.concat(baseModules)

//传递平台特有节点操作选项给工厂函数,返回patch
export const patch: Function = createPatchFunction({ nodeOps, modules })

从上可知patch函数是工厂函数createPatchFunction()返回的,createPatchFunction方法就是我们之前研究的diff算法的path.js中的内容,路径为src/core/vdom/patch.js

nodeOps中:主要是处理原生dom相关的方法

modules中:主要是处理属性更新的一些实现方法

3.vue中的原生事件初始化流程

创建demo

<body>
  <div id="app">
    <button @click="btnclick">按钮</button>
  </div>
  <script>
    const app = new Vue({
      el: '#app',
      methods: {
        btnclick(){
          console.log('1233')
        }
      },
    })
    console.log(app.$options.render)
  </script>
</body>

其中的render函数如下:

ƒ anonymous() {
    with(this){return _c('div',{attrs:{"id":"app"}},[
        _c('p',[_v("事件处理机制")]),_v(" "),
        _c('button',{on:{"click":btnclick}},[_v("按钮")]),_v(" ")
    ],1)}
}

事件也是作为属性处理的:src/platforms/web/runtime/modules/events.js

event.js主要内容:

function updateDOMListeners (oldVnode: VNodeWithData, vnode: VNodeWithData) {
  if (isUndef(oldVnode.data.on) && isUndef(vnode.data.on)) {
    return
  }
  const on = vnode.data.on || {}
  const oldOn = oldVnode.data.on || {}
  target = vnode.elm
  normalizeEvents(on)
  updateListeners(on, oldOn, add, remove, createOnceHandler, vnode.context)
  target = undefined
}

export default {
  create: updateDOMListeners, //这个create、update不是我们所说的生命周期
  update: updateDOMListeners}

初始化原生事件的监听

依据demo,初始化组件vm.__patch()__的时候(已经处理好原生dom和属性更新相关的一些代码) => createElm() => invokeCreateHooks() => updateDOMListeners() => updateListeners() => add() => 最终执行target.addEventListener监听事件

语言描述比较笼统,为了更好的了解,最好根据demo断点源码查看相关流程栈

4.vue中的自定义事件初始化流程

demo.html

<body>
  <div id="app">
    <comp @myclick="myclick"></comp>
  </div>
  <script>

    Vue.component('comp',{
      template:`
        <button @click="btnclick2">组件按钮</button>
      `,
      methods: {
        btnclick2(){
          this.$emit('myclick')
        }
      },
    })

    const app = new Vue({
      el: '#app',
      methods: {
        myclick(){
          console.log('898898')
        }
      },
    })
  </script>
</body>

自定义事件监听的入口:初始化init.js => initEvents方法 (src/core/instance/events.js)

export function initEvents (vm: Component) {
  vm._events = Object.create(null)
  vm._hasHookEvent = false
  // init parent attached events
  //谁派发谁监听
  //自定义组件中真正做事件监听的是事件派发着自己,也就是子组件
  const listeners = vm.$options._parentListeners
  if (listeners) {
    updateComponentListeners(vm, listeners)
  }
}

以上listeners:listeners: {myclick: ƒ}

updateComponentListeners

export function updateComponentListeners (
  vm: Component,
  listeners: Object,
  oldListeners: ?Object
) {
  target = vm
  updateListeners(listeners, oldListeners || {}, add, remove, createOnceHandler, vm)
  target = undefined
}

function add (event, fn) {
  target.$on(event, fn)
}

updateListeners => add(**)

export function updateListeners (
  on: Object,
  oldOn: Object,
  add: Function,
  remove: Function,
  createOnceHandler: Function,
  vm: Component
) {
  let name, def, cur, old, event
  //on: {myclick: ƒ}
  for (name in on) { //循环事件
    def = cur = on[name]
    old = oldOn[name]
    event = normalizeEvent(name)
    if (isUndef(old)) {
      if (isUndef(cur.fns)) {
        cur = on[name] = createFnInvoker(cur, vm)
      }
      if (isTrue(event.once)) {
        cur = on[name] = createOnceHandler(event.name, cur, event.capture)
      }
      add(event.name, cur, event.capture, event.passive, event.params)
    }
  }
}

5.v-model实现原理

语法糖 :value+@input

表单控件的v-model处理主要是在model方法中:src/platforms/web/compiler/directives/model.js

demo.html

<body>
  <div id="app">
    <!-- 表单控件 -->
    <input type="text" v-model="name" />
    <!-- 自定义事件 -->
    <my-input v-model="name"></my-input>
    <div>{{name}}</div>
  </div>
  <script>
    Vue.component('myInput',{
      props:['value'],
      template:`
        <input type="text" :value='value' @input="inputchange" />
      `,
      methods: {
        inputchange(e){
          this.$emit('input',e.target.value)
        },
      },
    })
    const app = new Vue({
      el: '#app',
      data:{
        name:'lxy'
      },
    })
  </script>
</body>

vue从模版到真实dom的过程:

(this._init() => Vue.$mount => compileToFunctions) =>const ast = parse(template.trim(), options)生成ast抽象语法树 => processAttrs方法 => 除去v-bind和v-on之外,使用addDirective =>普通指令会在对应的AST树上添加directives属性

接上ast相关完成之后继续执行const code = generate(ast, options)获取render函数的过程到以下步骤 => 到下图 => model方法,处理v-model中对表单控件的ast语法树进行特殊处理

感兴趣的可以看看不同的input场景具体处理方式:

最终生成render函数:语法糖设计主要部分就是domProps+on事件的处理

(function anonymous(
  ) {
  with(this){return _c('div',{attrs:{"id":"app"}},
  [_c('input',{
    directives:[{
      name:"model",
      rawName:"v-model",
      value:(name),
      expression:"name"
    }],
    attrs:{"type":"checkbox"},
    domProps:{"checked":Array.isArray(name)?_i(name,null)>-1:(name)},
    on:{"change":function($event){
      var $$a=name,$$el=$event.target,$$c=$$el.checked?(true):(false);
      if(Array.isArray($$a)){var $$v=null,$$i=_i($$a,$$v);
        if($$el.checked){$$i<0&&(name=$$a.concat([$$v]))}else{$$i>-1&&(name=$$a.slice(0,$$i).concat($$a.slice($$i+1)))}}
        else{name=$$c}}}}),_v(" "),
  _c('div',[_v(_s(name))])])}
  })

拓展:

针对自定义组件的v-model: src/core/vdom/create-component.js

// transform component v-model data into props & events
if (isDef(data.model)) {
  transformModel(Ctor.options, data)
}

// transform component v-model info (value and callback) into
// prop and event handler respectively.
function transformModel (options, data: any) {
  const prop = (options.model && options.model.prop) || 'value'
  const event = (options.model && options.model.event) || 'input'
  ;(data.attrs || (data.attrs = {}))[prop] = data.model.value
  const on = data.on || (data.on = {})
  const existing = on[event]
  const callback = data.model.callback
  if (isDef(existing)) {
    if (
      Array.isArray(existing)
        ? existing.indexOf(callback) === -1
        : existing !== callback
    ) {
      on[event] = [callback].concat(existing)
    }
  } else {
    on[event] = callback
  }
}

开发中自定义组件v-model的使用方法

Vue.component('myInput',{
  model:{
    prop:'checked',
    event:'change'
  },
  props:['checked'],
  template:`
    <input type="checkbox" :checked='checked' @change="$emit('change',$event.target.checked)" />
  `,
})