Vue2 源码分析
基于 Vue 2.6.10 版本
vue2.0 在实现“响应数据绑定”的同时引入了 virtual-dom
。
目录结构
- dist ---------------------------------- 构建后文件的输出目录
- xamples ----------------------------- 存放使用Vue开发的的例子
- flow --------------------------------- 类型声明(V3 使用的TS)
- packages --------------------------- xxx
- scripts ------------------------------ 构建相关的脚本
- package.json ----------------------- 项目依赖
- test --------------------------------- 测试文件相关
- src ---------------------------------- 源码目录
- platforms ------------------------- 平台相关的代码
- web
- compiler
- runtime
- entry-compiler.js ------------------ 只有 compiler
- entry-rumtime-with-compiler.js --- 独立构建
- entry-runtime.js ------------------- 运行时构建(默认构建方式)
- entry-server-basic-renderer.js
- entry-server-renderer.js
- weex
- ...
- web
- compiler ----------------------- 模板编译器,将 template 编译为 render()
- codegen -------------------- AST 转 render()
- parser ---------------------- 模板字符串转 AST
- index.js --------------------- 入口文件
- optimizer.js ---------------- 分析静态树,优化vdom渲染
-
core ----------------------- 通用的,与平台无关代码(核心)
- components ------------------ 抽象的通用组件
- global-api --------------------- Vue构造函数挂载全局方法(静态方法)或属性的代码
- instance ---------------------- Vue构造函数设计相关代码
- observer ----------------- 数据收集与订阅的代码
- util --- 工具方法
- vdom --- vdom创建与patch的代码
- index.js --- 入口文件
- config.js
- server ---- Vue SSR
- sfc --- 对.vue文件的解析逻辑
- parser.js
- shared --- 提供全局可用的工具函数
- constants.js --- 常量
- utils.js ----- 工具函数
- platforms ------------------------- 平台相关的代码
vue.js 的组成是由 core + 对应的
平台
相关代码构成。
独立构建
和运行时构建
只是 platforms 下 web 平台的两种选择
运行时构建,是不包含模板(template)到render函数的编译器
独立构建,包含模板(template)到render函数的编译器
dist 目录下,可以看到各种构建后的输出文件
dist目录- 针对不同环境的的构建输出,如:dev、prod、browser
- 有经过压缩的构建输出,如:min
- 有不同模块标准的构建输出,如:CommonJS、ES Module、UMD
- 有不同行为的构建输出,如:包含
runtime
与不包含runtime
new Vue()
在 package.json,scripts很多命令行脚本,任取其中一条,以此为入口开始探索 new Vue() 时会发生什么
scripts在 scripts/config.js 中,根据 TARGET: web-full-dev
定位到详细的信息:
// Runtime+compiler development build (Browser)
'web-full-dev': {
entry: resolve('web/entry-runtime-with-compiler.js'), // 构建入口
dest: resolve('dist/vue.js'), // 构建输出
format: 'umd', // 模块标准
env: 'development', // 构建模式(开发 or 生产)
alias: { he: './entity-decoder' },
banner
}
通过入口文件溯源到Vue构造函数
的整个流程
./instance/index
vue 核心代码,不平台共用的内容
按照上述路径,找到了Vue构造函数的定义,调用初始化函数 _init()
,导出 Vue
import { initMixin } from './init'
import { stateMixin } from './state'
import { renderMixin } from './render'
import { eventsMixin } from './events'
import { lifecycleMixin } from './lifecycle'
// Vue 构造函数
function Vue (options) {
// 初始化函数
this._init(options)
}
// Vue.prototype 挂载属性或方法
initMixin(Vue)
stateMixin(Vue)
eventsMixin(Vue)
lifecycleMixin(Vue)
renderMixin(Vue)
export default Vue
- initMixin()
export function initMixin(Vue) {
// Vue 原型上添加 _init
Vue.prototype._init = function () { }
}
- stateMixin()
export function stateMixin (Vue) {
Object.defineProperty(Vue.prototype, '$data', dataDef)
Object.defineProperty(Vue.prototype, '$props', propsDef)
Vue.prototype.$set = set
Vue.prototype.$delete = del
Vue.prototype.$watch = function () {}
}
- eventsMixin()
export function eventsMixin (Vue>) {
Vue.prototype.$on = function () { }
Vue.prototype.$once = function () {}
Vue.prototype.$off = function () { }
Vue.prototype.$emit = function () {}
}
- lifecycleMixin()
export function lifecycleMixin (Vue) {
Vue.prototype._update = function () { }
Vue.prototype.$forceUpdate = function () { }
Vue.prototype.$destroy = function () {}
}
- renderMixin()
export function renderMixin (Vue) {
// install runtime convenience helpers
installRenderHelpers(Vue.prototype);
Vue.prototype.$nextTick = function (fn: Function) {
return nextTick(fn, this)
}
Vue.prototype._render = function () {}
}
// installRenderHelpers/index.js
export function installRenderHelpers (target) {
// target is Vue.prototype
target._o = markOnce
target._n = toNumber
target._s = toString
target._l = renderList
target._t = renderSlot
target._q = looseEqual
target._i = looseIndexOf
target._m = renderStatic
target._f = resolveFilter
target._k = checkKeyCodes
target._b = bindObjectProps
target._v = createTextVNode
target._e = createEmptyVNode
target._u = resolveScopedSlots
target._g = bindObjectListeners
target._d = bindDynamicKeys
target._p = prependModifier
}
通过多个mixin,在 Vue.prototype 中挂载了若干方法和属性
属性或方法 | 文件 | 备注 |
---|---|---|
Vue.prototype._init() | initMixin | |
Vue.prototype.$set() | stateMixin | observer/index..js 中定义的方法 |
Vue.prototype.$delete() | stateMixin | observer/index..js 中定义的方法 |
Vue.prototype.$watch() | stateMixin | |
Vue.prototype.$data | stateMixin | 属性 |
Vue.prototype.$props | stateMixin | 属性 |
Vue.prototype.$on() | eventsMixin | |
Vue.prototype.$once() | eventsMixin | |
Vue.prototype.$off() | eventsMixin | |
Vue.prototype.$emit() | eventsMixin | |
Vue.prototype._update() | lifecycleMixin | |
Vue.prototype.$forceUpdate() | lifecycleMixin | |
Vue.prototype.$destroy() | lifecycleMixin | |
Vue.prototype.$nextTick() | renderMixin | |
Vue.prototype._render() | renderMixin | |
Vue.prototype._[x] | installRenderHelpers/index.js | [x] 表示若干单字母方法 |
core/index
vue 核心代码,平台共用的部分
// 在 Vue 上挂载静态方法和属性
initGlobalAPI(Vue);
// Vue 原型上挂载与SSR相关的内容
Object.defineProperty(Vue.prototype, '$isServer', {
get: isServerRendering
})
Object.defineProperty(Vue.prototype, '$ssrContext', {
get () {
return this.$vnode && this.$vnode.ssrContext
}
})
Object.defineProperty(Vue, 'FunctionalRenderContext', {
value: FunctionalRenderContext
})
Vue.version = '__VERSION__'
export default Vue
进入 initGlobalAPI()
export function initGlobalAPI (Vue: GlobalAPI) {
// config
const configDef = {}
configDef.get = () => config
// ...
Object.defineProperty(Vue, 'config', configDef);
Vue.util = {
warn,
extend,
mergeOptions,
defineReactive
}
Vue.set = set; // observer/index.js
Vue.delete = del; // observer/index.js
Vue.nextTick = nextTick;
// 2.6 explicit observable API
Vue.observable = (obj: T): T => {
observe(obj)
return obj
}
Vue.options = Object.create(null); // 静态属性 options
ASSET_TYPES.forEach(type => {
Vue.options[type + 's'] = Object.create(null);
// options: { components: {}, filters: {}, directives: {} }
})
// _base 关联到自身
Vue.options._base = Vue;
extend(Vue.options.components, builtInComponents)
// 可以继续深入...
initUse(Vue)
initMixin(Vue)
initExtend(Vue)
initAssetRegisters(Vue)
}
./runtime/index
安装平台特有工具
// 安装平台特有的工具
Vue.config.mustUseProp = mustUseProp
Vue.config.isReservedTag = isReservedTag
Vue.config.isReservedAttr = isReservedAttr
Vue.config.getTagNamespace = getTagNamespace
Vue.config.isUnknownElement = isUnknownElement
// 安装平台运行时的指令和组件
extend(Vue.options.directives, platformDirectives)
extend(Vue.options.components, platformComponents)
// install platform patch function
Vue.prototype.__patch__ = inBrowser ? patch : noop;
// public mount method
Vue.prototype.$mount = function () {}
// other code....
export default Vue
web/entry-runtime-with-compiler.js
// 缓存 $mount (来自 web/runtime/index.js)
const mount = Vue.prototype.$mount;
// 覆盖 $mount
Vue.prototype.$mount = function () {
const options = this.$options
// 没有render函数
if (!options.render) { }
return mount.call(this, el, hydrating)
}
// Vue 挂载 compile
// compileToFunctions : 将模板编译为 render 函数
Vue.compile = compileToFunctions
export default Vue
以上还原了 Vue 构造函数的细节
Vue实例代码分析
通过 new Vue()
创建一个 vue 实例,通过代码来探究在创建实例时,做了哪些事情?如:怎么就实现了响应式数据绑定?
// 子组件
const child = {
props: {
msg: String,
count: Number,
},
template: `<div><p>{{msg}}</p><p>{{count}}</p></div>`,
};
// 父组件
const app = Vue.new({
el: '#app',
data: {
message: '父组件值',
count: 1,
},
methods: {},
components: {
child,
},
});
<div id="app">
<child :msg="message" :count="count"></child>
</div>
_init()
调用 new Vue(),首先进入 Vue.prototype._init() (构造函数挂载的第一个方法)
let uid = 0;
Vue.prototype._init = function (options) {
const vm = this
// vue uid
vm._uid = uid++
// a flag to avoid this being observed
vm._isVue = true
// merge options
if (options && options._isComponent) {
initInternalComponent(vm, options)
} else {
vm.$options = mergeOptions(
resolveConstructorOptions(vm.constructor),
options || {},
vm
);
}
// 省略部分代码
vm._self = vm
initLifecycle(vm)
initEvents(vm)
initRender(vm)
callHook(vm, 'beforeCreate');
initInjections(vm)
initState(vm)
initProvide(vm)
callHook(vm, 'created');
if (vm.$options.el) {
vm.$mount(vm.$options.el)
}
}
_init() 在开始时,在 this 对象上定义了两个属性 _uid 和 _isVue,
然后判断有没有定义 options._isComponent这里会走 else 分支,即 mergeOptions()
接着调用initLifecycle、initEvents、initRender、initState,且在 initState 前后分别回调了生命周期钩子 beforeCreate 和 created。看到这里也就明白了为什么 created 的时候不能操作DOM了。因为这个时候还没有渲染真正的DOM元素到文档中。created 仅仅代表数据状态的初始化完成
initState(vm)
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)
}
}
initData(vm)
data 属性存在,调用 initData(vm)
function initData (vm: Component) {
let data = vm.$options.data;
// _data 是 data 的一份拷贝
data = vm._data = typeof data === 'function' ? getData(data, vm) : data || {}
// 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 (props && hasOwn(props, key)) {
// warning code......
} else if (!isReserved(key)) { // key 不是以 $ 或 _ 开头
// 将 data 中的属性,都通过Object.defineProperty()绑定到了vue实例上,可以直接通过‘this.属性’进行访问
proxy(vm, `_data`, key)
}
}
// 第一步:observe data (双向数据绑定的核心)
observe(data, true /* asRootData */); // data 就是 Vue 实例中的 data
}
observe(data, true)
// 源码 :src/core/observer/index.js
export function observe(value: any, asRootData: ?boolean): Observer | void {
// 不是object 或是 VNode 实例时,直接返回
if (!isObject(value) || value instanceof VNode) {
return
}
let ob: Observer | void;
//TODO: __ob__ 在哪里定义的?
if (hasOwn(value, '__ob__') && value.__ob__ instanceof Observer) {
ob = value.__ob__
} else if (
shouldObserve &&
!isServerRendering() &&
(Array.isArray(value) || isPlainObject(value)) &&
Object.isExtensible(value) &&
!value._isVue
) {
// 第二步
ob = new Observer(value)
}
if (asRootData && ob) {
ob.vmCount++
}
// 返回 Observer 实例
return ob
}
Observer()
export class Observer {
value: any;
dep: Dep;
vmCount: number;
constructor(value: any) {
this.value = value;
// // TODO: 猜想:Dep实例是 Watcher 和 Observer 之间的纽带
this.dep = new Dep();
this.vmCount = 0;
// 把自身 this 添加到 value 的 __ob__ 属性上
// 即:vue 实例的 data.__ob__ 关联到了 Observer 实例
def(value, '__ob__', this); // def => Object.defineProperty 封装
// 对 value 的类型进行判断(数组和对象的处理方式不同)
if (Array.isArray(value)) { // 如果是数组
if (hasProto) {
protoAugment(value, arrayMethods)
} else {
copyAugment(value, arrayMethods, arrayKeys)
}
// 数组处理
this.observeArray(value)
} else {
// 对象处理
this.walk(value)
}
}
// 对 Object 进行响应式处理
// eg: obj = {a: 1, b:2, c:3 }
walk(obj: Object) {
const keys = Object.keys(obj)
for (let i = 0; i < keys.length; i++) {
defineReactive(obj, keys[i]); // 对 obj 的属性循环调用 defineReactive
}
}
// 对数组进行响应式处理
observeArray(items) {
for (let i = 0, l = items.length; i < l; i++) {
observe(items[i])
}
}
}
defineReactive(obj, propName)
export function defineReactive(
obj: Object,
key: string,
val: any,
customSetter?: ?Function,
shallow?: boolean
) {
// 每个对象属性都会有一个 Dep 实例,用来保存依赖(Watcher 对象)
const dep = new Dep()
const property = Object.getOwnPropertyDescriptor(obj, key)
if (property && property.configurable === false) { // 如果该属性是不可配置
return
}
// cater for pre-defined getter/setters
const getter = property && property.get
const setter = property && property.set
if ((!getter || setter) && arguments.length === 2) {
val = obj[key]
}
// 结合 observe(),将对象的对象也转变成Observer对象
let childOb = !shallow && observe(val); // 属性值可能是个对象
// 最核心部分:调用 Object.defineProperty() 给 data(Vue 实例中的data)的每个属性添加 getter 和 setter 方法
Object.defineProperty(obj, key, {
enumerable: true,
configurable: true,
get: function reactiveGetter() {
const value = getter ? getter.call(obj) : val;
// TODO: Dep.target 在哪里设置的?Dep.target 本质是什么?
if (Dep.target) {
dep.depend(); // 依赖收集
if (childOb) {
childOb.dep.depend(); // 处理子元素的依赖 watcher
if (Array.isArray(value)) { //如果是数组,进一步处理
dependArray(value)
}
}
}
return value
},
set: function reactiveSetter(newVal) {
const value = getter ? getter.call(obj) : val
if (newVal === value || (newVal !== newVal && value !== value)) {
return
}
if (setter) {
setter.call(obj, newVal)
} else {
val = newVal
}
// 对数据重新 observe(), 更新数据的依赖关系
childOb = !shallow && observe(newVal);
// TODO: 留一个疑问?
dep.notify(); // 通知 dep 进行数据更新
}
})
}
vm.$mount
(vm.$options.el)
initData(vm) 执行完成后,完成了响应数据的构建,回到_init()中继续执行callHook(vm, 'created'); 调用钩子created,然后执行 vm.$mount() 方法
mount// web/entry-runtime-with-compiler.js
Vue.prototype.$mount = function (
el?: string | Element,
hydrating?: boolean
): Component {
// 1、获取挂载的 el 节点
el = el && query(el);
// el 不能是 <body> 或 <html>
const options = this.$options;
// 没有render函数
if (!options.render) {
let template = options.template;
if (template) { // 存在template
if (typeof template === 'string') {
if (template.charAt(0) === '#') {
template = idToTemplate(template);
}
} else if (template.nodeType) {
template = template.innerHTML;
} else {
if (process.env.NODE_ENV !== 'production') {
warn('invalid template option:' + template, this)
}
return this;
}
} else if (el) {
template = getOuterHTML(el); // 直接获取页面的html代码作为模板
}
// 编译template
if (template) {
// compileToFunctions 将 模板编译为AST,经过静态优化,最后处理为render函数
const { render, staticRenderFns } = compileToFunctions(template, {
outputSourceRange: process.env.NODE_ENV !== 'production',
shouldDecodeNewlines,
shouldDecodeNewlinesForHref,
delimiters: options.delimiters,
comments: options.comments
}, this);
options.render = render
options.staticRenderFns = staticRenderFns
}
}
// runtime/index.js
return mount.call(this, el, hydrating)
}
mountComponent()
生成render 函数之后,调用 mountComponent()
- 首先调用 beforeMount()
- 接着执行 new Watcher(vm, updateComponent, noop, {}, true)
- 最后callHook(vm, ''mounted),进行组件挂载。
export function mountComponent (
vm: Component, // vue 实例
el: ?Element, // 挂载节点
hydrating?: boolean
): Component {
vm.$el = el;
if (!vm.$options.render) { // 没有render函数
vm.$options.render = createEmptyVNode;
// .....
}
//1、 首先
callHook(vm, 'beforeMount');
// ......
let updateComponent = () => {
vm._update(vm._render(), hydrating)
}
}
// 2、然后
new Watcher(vm, updateComponent, noop, {
before () {
if (vm._isMounted && !vm._isDestroyed) { // vm 未挂载且未销毁
callHook(vm, 'beforeUpdate')
}
}
}, true /* isRenderWatcher */);
hydrating = false;
// 3、最后
if (vm.$vnode == null) {
vm._isMounted = true
callHook(vm, 'mounted')
}
return vm;
}
new Watcher(....)
export default class Watcher {
constructor (vm, expOrFn, cb, options, isRenderWatcher ) {
this.vm = vm
if (isRenderWatcher) {
vm._watcher = this;// watcher实例 绑定到 vue 实例的 _watcher 属性上
}
vm._watchers.push(this); // 将当前 watcher实例推送到对应的 Vue 实例中
// options
if (options) {
this.deep = !!options.deep
this.user = !!options.user
this.lazy = !!options.lazy
this.sync = !!options.sync
this.before = options.before
} else {
this.deep = this.user = this.lazy = this.sync = false
}
// 省略部分内容
// parse expression for getter
if (typeof expOrFn === 'function')
this.getter = expOrFn;
} else {
this.getter = parsePath(expOrFn);
}
// 如果 lazy 不为 true, 则执行 get 函数进行依赖收集
this.value = this.lazy ? undefined : this.get()
}
}
在构造函数由于 this.lazy=false; 所以 this.value = this.lazy ? undefined : this.get(); 将会调用 get() 方法获取值
// 执行 getter(),进行依赖收集
get () {
// 设置全局变量 Dep.target, 将当前的 watcher 实例保存在这个全局变量中
pushTarget(this);
let value;
const vm = this.vm; // vue 实例
try {
// 调用 getter 函数,进入 get() 进行依赖收集操作
// 此时 this.getter() 就是:() => { vm._update(vm._render(), hydrating) }
value = this.getter.call(vm, vm);
} catch (e) {
// ......
} finally {
if (this.deep) {
traverse(value);
}
popTarget(); // 将全局变量 Dep.target 置为 null
this.cleanupDeps()
}
return value
}
vm._render()
返回一个 VNode (虚拟节点)
// instance/render.js
Vue.prototype._render = function (): VNode {
const vm: Component = this
const { render, _parentVnode } = vm.$options
if (_parentVnode) {
vm.$scopedSlots = normalizeScopedSlots(
_parentVnode.data.scopedSlots,
vm.$slots,
vm.$scopedSlots
)
}
vm.$vnode = _parentVnode
let vnode
try {
currentRenderingInstance = vm
vnode = render.call(vm._renderProxy, vm.$createElement)
} catch (e) {
handleError(e, vm, `render`)
} finally {
currentRenderingInstance = null
}
if (Array.isArray(vnode) && vnode.length === 1) {
vnode = vnode[0]
}
// ...
// set parent
vnode.parent = _parentVnode
return vnode
}
vm._update()
// instance/lifecycle.js
Vue.prototype._update = function (vnode: VNode, hydrating?: boolean) {
const vm: Component = this
const prevEl = vm.$el
const prevVnode = vm._vnode
const restoreActiveInstance = setActiveInstance(vm)
vm._vnode = vnode
if (!prevVnode) {
// 首次渲染
vm.$el = vm.__patch__(vm.$el, vnode, hydrating, false /* removeOnly */)
} else {
// 更新
vm.$el = vm.__patch__(prevVnode, vnode)
}
restoreActiveInstance()
// update __vue__ reference
if (prevEl) {
prevEl.__vue__ = null
}
if (vm.$el) {
vm.$el.__vue__ = vm
}
// if parent is an HOC, update its $el as well
if (vm.$vnode && vm.$parent && vm.$vnode === vm.$parent._vnode) {
vm.$parent.$el = vm.$el
}
}
网友评论