什么是数据驱动
视图由数据驱动生成,意思就是只对数据修改,而不是直接操作DOM。
new Vue发生了什么
如何调试
可以通过vue-cli新建一个项目,这里没有选择装最新的`vue-cli@3.0,而是安装了vue-cli@2.9.6`
1 | npm install -g vue-cli@2.9.6 |
看一下项目根目录的package.json文件的scripts
1 | "scripts": { |
也就是npm run dev的时候,使用的打包配置文件是webpack.dev.conf.js,而在这个配置文件中又merge了webpack.base.conf的配置。

resolve.alias用于创建 import 或 require 的别名,来确保模块引入变得更简单,在这里的意思就是
1 | import Vue from 'vue'; |
所以在vue.esm.js文件里的构造函数里打debugger

然后执行npm run dev,就可以断点调试了。
我们的模板
1 | import Vue from 'vue' |
开始
一、入口
1 | function Vue (options) { |
执行_init方法,传入option参数
二、调用_init方法
1 | Vue.prototype._init = function (options) { |
initState方法
1 | function initState (vm) { |
initData方法,这个方法判断了method和props是否包含data对象里的键名,最后他们都会挂载到vm实例上,所以不能重复。
1 | function initData (vm) { |
并且proxy方法,这个方法做的事情就是为vm实例上的属性,设置了属性描述符get与set。
1 | function proxy (target, sourceKey, key) { |
mergeOptions合并配置信息- 初始化操作
- 最后
vm.$mount,手动地挂载一个未挂载的实例。
实例挂载的实现
上面说了vm.$mount方法,那看一下它做了什么
1 | Vue.prototype.$mount = function ( |
最后执行mount.call(this, el, hydrating)方法
1 | function ( |
又走进mountComponent(this, el, hydrating)
1 | function mountComponent ( |
vm._update(vm._render(), hydrating);方法走进去,一直往下走找到了下面这个方法insert,在这里把内容插入body里。
1 | function insert (parent, elm, ref$$1) { |
所以实例挂载主要做的事情就是
做一些基础判断,比如不允许绑在
body和html上等等vm.render方法先生存虚拟节点(vnode),在实例化一个渲染watcher,回调函数是updateComponent之后
vm.update更新DOM
vm._render方法
上面vm实例上的_render方法,会生成一个虚拟节点(vnode)。
1 | Vue.prototype._render = function () { |
Virtual DOM
虚拟DOM,其实就是用个对象去描述DOM结构。目的是为了避免频繁操作DOM。而Virtual DOM 映射到真实的 DOM 实际上要经历 VNode 的 create、diff、patch 等过程。
createElement方法
生成vnode的方法
1 | function createElement ( |
vm._update方法
这个方法会在首次渲染和数据更新的时候调用
1 | Vue.prototype._update = function (vnode, hydrating) { |
这边走进vm.$el = vm.__patch__(vm.$el, vnode, hydrating, false /* removeOnly */);
1 | ... |
其中有一段,通过 emptyNodeAt 方法把 oldVnode 转换成 VNode 对象,然后再调用 createElm 方法。走进createElm
1 | function createElm ( |
createElm方法中的部分内容:
1 | //这里创建了一个占位符,我们这边是创建了一个p标签 |
上面代码中的createElement方法
1 | function createElement$1 (tagName, vnode) { |
走进createChildren方法,这个实际上是遍历子虚拟节点,递归调用 createElm
1 | function createChildren (vnode, children, insertedVnodeQueue) { |
这边再次进入createElm方法
1 | vnode.elm = nodeOps.createTextNode(vnode.text); |
这里的nodeOps.createTextNode(vnode.text)是通过document.createTextNode(text)创建了一个文本节点,并且赋值给了 vnode.elm,最后insert(parentElm, vnode.elm, refElm);插入到父元素里。下面是insert方法:
1 | function insert (parent, elm, ref$$1) { |
最后通过下面方法调用,将元素插入到body下面
1 | insert(parentElm, vnode.elm, refElm); |
1 | function insert (parent, elm, ref$$1) { |
1 | function insertBefore (parentNode, newNode, referenceNode) { |
new Vue之后发生的事情流程图

每个阶段做的事情:
init:初始化阶段,为Vue添加原型方法与静态方法,将new Vue传入的参数添加到实例上,并且判断是否有重复的键名。
$mount:做一些基础的判断,(比如判断替换模板不能是html与body之类)
compile:模板解析阶段
render:生成vnode的方法
vnode:生成的虚拟节点
patch:这个阶段创建占位符,并且遍历子节点(深度优先)添加到占位符里,最后添加到DOM中
DOM:生成DOM
补充
module字段和main字段
1 | "main": "dist/vue.runtime.common.js", |
在打包构建的时候:
在CommonJS 规范中,模块只能通过 exports 对象向外暴露属性。所有要暴露的方法、变量等都只能作为 exports 对象的一个属性出现。所以打包工具并不知道我们代码中最终会用到模块中的哪些方法。为了安全起见,整个模块的代码都被包含在了最终生成在 bundle 中。
在 ES6 中的 import 和 export 都是静态的。静态意味着一个模块要暴露或引入的所有方法在编译阶段就全部确定了,之后不能再改变。这样子打包的时候可以将没用到的模块方法剔除,这就是Tree Shaking。
所以:
main: 字段指向的应该是编译后生成的 ES5 版本的代码。
module: 字段要指向的应该是一个基于 ES6 模块规范的使用ES5语法书写的模块。