在开始以前,阅读源码你须要有扎实的基本功,还要有耐心,能把握全局,不要扣细节!html
├── build --------------------------------- 构建相关的文件 ├── dist ---------------------------------- 构建后文件的输出目录 ├── examples ------------------------------ 存放使用Vue开发的的例子 ├── flow ---------------------------------- 类型声明,使用开源项目 [Flow](https://flowtype.org/) ├── package.json -------------------------- 项目依赖 ├── test ---------------------------------- 包含全部测试文件 ├── src ----------------------------------- 这个是咱们最应该关注的目录,包含了源码 │ ├──platforms --------------------------- 包含平台相关的代码 │ │ ├──web ----------------------------- 包含了不一样构建的包的入口文件 │ │ | ├──entry-runtime.js ---------------- 运行时构建的入口,输出 dist/vue.common.js 文件,不包含模板(template)到render函数的编译器,因此不支持 `template` 选项,咱们使用vue默认导出的就是这个运行时的版本。你们使用的时候要注意 │ │ | ├── entry-runtime-with-compiler.js -- 独立构建版本的入口,输出 dist/vue.js,它包含模板(template)到render函数的编译器 │ ├── compiler -------------------------- 编译器代码的存放目录,将 template 编译为 render 函数 │ │ ├── parser ------------------------ 存放将模板字符串转换成元素抽象语法树的代码 │ │ ├── codegen ----------------------- 存放从抽象语法树(AST)生成render函数的代码 │ │ ├── optimizer.js ------------------ 分析静态树,优化vdom渲染 │ ├── core ------------------------------ 存放通用的,平台无关的代码 │ │ ├── observer ---------------------- 反应系统,包含数据观测的核心代码 │ │ ├── vdom -------------------------- 包含虚拟DOM建立(creation)和打补丁(patching)的代码 │ │ ├── instance ---------------------- 包含Vue构造函数设计相关的代码 │ │ ├── global-api -------------------- 包含给Vue构造函数挂载全局方法(静态方法)或属性的代码 │ │ ├── components -------------------- 包含抽象出来的通用组件 │ ├── server ---------------------------- 包含服务端渲染(server-side rendering)的相关代码 │ ├── sfc ------------------------------- 包含单文件组件(.vue文件)的解析逻辑,用于vue-template-compiler包 │ ├── shared ---------------------------- 包含整个代码库通用的代码复制代码
使用 new
操做符来调用 Vue
,Vue
是一个构造函数,了解了目录结构,下面来看下下入口文件vue
打开package.jsonnode
当咱们运行npm run dev,看看干了啥,rollup也是相似webpack的打包工具,根据react
TARGET=web-full-devwebpack
打开入口文件找到了web/entry-runtime-with-compiler.js
web
依照以上查找路径,咱们找到了Vue构造函数算法
定义了构造函数,引入依赖,调用初始化函数,最后导出Vueexpress
initMixin(Vue) stateMixin(Vue) eventsMixin(Vue) lifecycleMixin(Vue) renderMixin(Vue) export default Vue复制代码
打开这五个文件,找到相应的方法,你会发现,这些方法的做用,就是在 Vue 的原型 prototype 上挂载方法或属性npm
Vue.prototype._init = function (options) {} 复制代码
Vue.prototype.$data Vue.prototype.$props Vue.prototype.$set = set Vue.prototype.$delete = del Vue.prototype.$watch = function(){} 复制代码
Vue.prototype.$on Vue.prototype.$once Vue.prototype.$off Vue.prototype.$emit 复制代码
Vue.prototype._update Vue.prototype.$forceUpdate Vue.prototype.$destroy 复制代码
Vue.prototype.$nextTick Vue.prototype._render Vue.prototype._o = markOnce Vue.prototype._n = toNumber Vue.prototype._s = toString Vue.prototype._l = renderList Vue.prototype._t = renderSlot Vue.prototype._q = looseEqual Vue.prototype._i = looseIndexOf Vue.prototype._m = renderStatic Vue.prototype._f = resolveFilter Vue.prototype._k = checkKeyCodes Vue.prototype._b = bindObjectProps Vue.prototype._v = createTextVNode Vue.prototype._e = createEmptyVNode Vue.prototype._u = resolveScopedSlots Vue.prototype._g = bindObjectListeners 复制代码
引入依赖,在Vue上挂载静态方法和属性json
import { initGlobalAPI } from './global-api/index' import { isServerRendering } from 'core/util/env' initGlobalAPI(Vue) Object.defineProperty(Vue.prototype, '$isServer', { get: isServerRendering }) Object.defineProperty(Vue.prototype, '$ssrContext', { get () { /* istanbul ignore next */ return this.$vnode && this.$vnode.ssrContext } }) Vue.version = '__VERSION__' export default Vue复制代码
Vue.config Vue.util = util Vue.set = set Vue.delete = del Vue.nextTick = util.nextTick Vue.options = { components: { KeepAlive }, directives: {}, filters: {}, _base: Vue } Vue.use Vue.mixin Vue.cid = 0 Vue.extend Vue.component = function(){} Vue.directive = function(){} Vue.filter = function(){} 复制代码
Vue.prototype.$isServer Vue.version = '__VERSION__' 复制代码
Vue.config.mustUseProp = mustUseProp Vue.config.isReservedTag = isReservedTag Vue.config.isReservedAttr = isReservedAttr Vue.config.getTagNamespace = getTagNamespace Vue.config.isUnknownElement = isUnknownElement // 安装平台特定的 指令 和 组件 Vue.options = { components: { KeepAlive, Transition, TransitionGroup }, directives: { model, show }, filters: {}, _base: Vue } Vue.prototype.__patch__ Vue.prototype.$mount复制代码
web-runtime.js
文件的 $mount
函数,const mount = Vue.prototype.$mountcompile
而后覆盖覆盖了 Vue.prototype.$mount
template
编译为render函数。到这整个Vue构造函数就还原了
index.html
<!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8"> <title>Vue.js grid component example</title> </head> <body> <div id="app"> <ol> <li v-for="todo in todos"> {{ todo.text }} </li> </ol> <Child></Child> </div> </body> </html> 复制代码
grid.js
let Child = { data: function() { return {child: '你好哈'} }, template: '<div>{{child}}</div>' } new Vue({ el: '#app', data: { todos: [ {text: '学习 JavaScript'}, {text: '学习 Vue'}, {text: '整个牛项目'}] }, components: {'Child': Child} })复制代码
Vue.prototype._init = function (options) { const vm= this vm._uid = uid++ let startTag, endTag vm._isVue = true if (options && options._isComponent) { initInternalComponent(vm, options) } else { vm.$options = mergeOptions( resolveConstructorOptions(vm.constructor), options || {}, vm ) } vm._renderProxy = 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
分支,也就是这段代码:
vm.$options = mergeOptions( resolveConstructorOptions(vm.constructor), options || {}, vm )mergeOptions使用策略模式合并传入的options和Vue.options复制代码
initLifecycle
、
initEvents
、
initRender、initState
,且在
initState
先后分别回调了生命周期钩子
beforeCreate
和
created,
看到这里,也就明白了为何 created 的时候不能操做DOM了。由于这个时候尚未渲染真正的DOM元素到文档中。
created
仅仅表明数据状态的初始化完成。
重点看下initState()
initState (vm) { 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) } }复制代码
function initData (vm: Component) { let data = vm.$options.data data = vm._data = typeof data === 'function' ? getData(data, vm) : data || {} 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] proxy(vm, `_data`, key) } observe(data, true /* asRootData */) } 复制代码
proxy (target, sourceKey, key) { sharedPropertyDefinition.get = function proxyGetter () { return this[sourceKey][key] } sharedPropertyDefinition.set = function proxySetter (val) { this[sourceKey][key] = val } Object.defineProperty(target, key, sharedPropertyDefinition) }复制代码
class Observer { constructor(value) { this.value = value this.dep = new Dep() this.vmCount = 0 def(value, '__ob__', this) if (Array.isArray(value)) { const augment = hasProto ? protoAugment : copyAugment augment(value, arrayMethods, arrayKeys) this.observeArray(value) } else { this.walk(value) } } walk(obj: Object) { const keys = Object.keys(obj) for (let i = 0; i < keys.length; i++) { defineReactive(obj, keys[i], obj[keys[i]]) } } observeArray(items) { for (let i = 0, l = items.length; i < l; i++) { observe(items[i]) } } } 复制代码
在 Observer
类中,咱们使用 walk
方法对数据data的属性循环调用 defineReactive
方法,defineReactive
方法很简单,仅仅是将数据data的属性转为访问器属性,并对数据进行递归观测,不然只能观测数据data的直属子属性。这样咱们的第一步工做就完成了,当咱们修改或者获取data属性值的时候,经过 get
和 set
即能获取到通知。
function defineReactive ( obj, key, val, customSetter, shallow ) { const dep = new Dep() let childOb = !shallow && observe(val) Object.defineProperty(obj, key, { enumerable: true, configurable: true, get: function reactiveGetter () { const value = getter ? getter.call(obj) : val if (Dep.target) { dep.depend() if (childOb) { childOb.dep.depend() 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 } val = newVal childOb = !shallow && observe(newVal) dep.notify() } }) } 复制代码
if (Array.isArray(value)) { const augment = hasProto ? protoAugment : copyAugment augment(value, arrayMethods, arrayKeys) this.observeArray(value) } else { this.walk(value) }export const arrayMethods = Object.create(arrayProto) ;[ 'push', 'pop', 'shift', 'unshift', 'splice', 'sort', 'reverse' ] .forEach(function (method) { const original = arrayProto[method] def(arrayMethods, method, function mutator (...args) { const result = original.apply(this, args) const ob = this.__ob__ let inserted switch (method) { case 'push': case 'unshift': inserted = args break case 'splice': inserted = args.slice(2) break } if (inserted) ob.observeArray(inserted) // notify change ob.dep.notify() return result }) }) 复制代码
Vue.prototype._init = function (options) { const vm= this vm._uid = uid++ let startTag, endTag vm._isVue = true if (options && options._isComponent) { initInternalComponent(vm, options) } else { vm.$options = mergeOptions( resolveConstructorOptions(vm.constructor), options || {}, vm ) } vm._renderProxy = 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) } }复制代码
进入$mount会先获取挂载el节点,而后先判断有没有传入render方法,没有在去找有没有传入template,
Vue.prototype.$mount = function ( el, hydrating ){ el = el && query(el) const options = this.$options if (!options.render) { let template = options.template if (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) } if (template) { const { render, staticRenderFns } = compileToFunctions(template, { shouldDecodeNewlines, delimiters: options.delimiters, comments: options.comments }, this) options.render = render options.staticRenderFns = staticRenderFns if (process.env.NODE_ENV !== 'production' && config.performance && mark) { mark('compile end') measure(`${this._name} compile`, 'compile', 'compile end') } } } return mount.call(this, el, hydrating) } 复制代码
function() { with (this) { return _c('div', { attrs: { "id": "app" } }, [_c('ol', _l((this.todos), function(todo) { return _c('li', [_v("\n " + _s(todo.text) + "\n ")]) })), _v(" "), _c('child')], 1) } }复制代码
function mountComponent ( vm, el, hydrating ): Component { vm.$el = el if (!vm.$options.render) { vm.$options.render = createEmptyVNode } callHook(vm, 'beforeMount') let updateComponent = () => { vm._update(vm._render(), hydrating) } vm._watcher = new Watcher(vm, updateComponent, noop) hydrating = false if (vm.$vnode == null) { vm._isMounted = true callHook(vm, 'mounted') } return vm } 复制代码
查看Watcher代码
class Watcher { constructor ( vm, expOrFn, cb, options ) { this.vm = vm vm._watchers.push(this) if (options) { this.deep = !!options.deep this.user = !!options.user this.lazy = !!options.lazy this.sync = !!options.sync } else { this.deep = this.user = this.lazy = this.sync = false } this.cb = cb this.id = ++uid // uid for batching this.active = true this.dirty = this.lazy // for lazy watchers this.deps = [] this.newDeps = [] this.depIds = new Set() this.newDepIds = new Set() this.expression = process.env.NODE_ENV !== 'production' ? expOrFn.toString() : '' // parse expression for getter if (typeof expOrFn === 'function') { this.getter = expOrFn } else { this.getter = parsePath(expOrFn) } this.value = this.lazy ? undefined : this.get() } get () { pushTarget(this) let value const vm = this.vm value = this.getter.call(vm, vm) return value } addDep (dep: Dep) { const id = dep.id if (!this.newDepIds.has(id)) { this.newDepIds.add(id) this.newDeps.push(dep) if (!this.depIds.has(id)) { dep.addSub(this) } } } evaluate () { this.value = this.get() this.dirty = false } depend () { let i = this.deps.length while (i--) { this.deps[i].depend() } } } 复制代码
执行构造函数因为 this.lazy=false; this.value = this.lazy ? undefined : this.get();
执行get方法 其中pushTarget(this),给Dep.target添加静态属性this(当前new Watcher()实例 )
function pushTarget (_target) {
if (Dep.target) targetStack.push(Dep.target)
Dep.target = _target
}
get () { pushTarget(this) let value const vm = this.vm value = this.getter.call(vm, vm) return value } 复制代码
接着执行 this.getter.call(vm, vm)复制代码
this.getter就是
updateComponent = () => {
vm._update(vm._render(), hydrating)
} 复制代码
Vue.prototype._render = function (){ const vm = this const { render, staticRenderFns, _parentVnode } = vm.$options let vnode = render.call(vm._renderProxy, vm.$createElement) return vnode } 复制代码
开始执行以前编译好的render函数了,在执行render函数时,经过获取todos属性等,触发相应
function() { with (this) { return _c('div', { attrs: { "id": "app" } }, [_c('ol', _l((this.todos), function(todo) { return _c('li', [_v("\n " + _s(todo.text) + "\n ")]) })), _v(" "), _c('child')], 1) } }复制代码
的get方法,这个时候Dep.target已经存在静态属性,Watcher实例了
因此相应的dep实例就会收集对应的Watcher实例了
复制代码
执行完以后返回vnode,
updateComponent = () => { vm._update(vm._render(), hydrating) } 其中vm._render()执行render函数返回vnode做为参数 接下来执行vm._update 这是首次渲染,因此执行 vm.$el = vm.__patch__( vm.$el, vnode, hydrating, false, vm.$options._parentElm, vm.$options._refElm )复制代码
Vue.prototype._update = function (vnode, hydrating) { const vm: Component = this if (vm._isMounted) { callHook(vm, 'beforeUpdate') } const prevEl = vm.$el const prevVnode = vm._vnode const prevActiveInstance = activeInstance activeInstance = vm vm._vnode = vnode if (!prevVnode) { vm.$el = vm.__patch__( vm.$el, vnode, hydrating, false, vm.$options._parentElm, vm.$options._refElm ) vm.$options._parentElm = vm.$options._refElm = null } else { vm.$el = vm.__patch__(prevVnode, vnode) } activeInstance = prevActiveInstance if (prevEl) { prevEl.__vue__ = null } if (vm.$el) { vm.$el.__vue__ = vm } if (vm.$vnode && vm.$parent && vm.$vnode === vm.$parent._vnode) { vm.$parent.$el = vm.$el } } 复制代码
若是尚未 prevVnode
说明是首次渲染,直接建立真实DOM。若是已经有了 prevVnode
说明不是首次渲染,那么就采用 patch
算法进行必要的DOM操做。这就是Vue更新DOM的逻辑。只不过咱们没有将 virtual DOM 内部的实现。
当改变属性值时,会触发对应的属性的set方法,因为以前执行render的时候触发了get,收集了对应的Watcher,因此改变值时触发set,通知以前收集的Watcher实例执行,从新计算render方法进行patch操做
最后盗取一张图:
写了半天,实在写不下去了,之后有好的语言,再来整理吧!