这里使用的是vue2.6.9版本,可以在GitHub上下载
调试vue项目的方式
- 安装依赖:npm i
- 安装打包工具:npm i rollup -g
- 修改package.json里面dev脚本
"dev": "rollup -w -c scripts/config.js --sourcemap --environment TARGET:web-full-dev"
- 执行打包: npm run dev
- 修改samples里面的文件,引用新生成的vue.js
整体启动顺序(主线任务):
1、根据package.json 中scripts 的dev命令可知,TARGET:web-full-dev
2、进入scripts/config.js,找到web-full-dev,可以知道它的 entry 是 web/entry-runtime-with-compiler.js
3、进入 platforms/web/entry-runtime-with-compiler.js
- 实现$mounte(支线任务) : mountComponent
- 全局API初始化(支线任务):initGlobalAPI(Vue) --- srccoreglobal-apiindex.js
function Vue (options) { if (process.env.NODE_ENV !== 'production' && !(this instanceof Vue) ) { warn('Vue is a constructor and should be called with the `new` keyword') } this._init(options) } initMixin(Vue) stateMixin(Vue) eventsMixin(Vue) lifecycleMixin(Vue) renderMixin(Vue)
initMixin(Vue)
initProxy(vm) initLifecycle(vm) initEvents(vm) initRender(vm) callHook(vm, 'beforeCreate') initInjections(vm) // resolve injections before data/props initState(vm) initProvide(vm) // resolve provide after data/props callHook(vm, 'created')
initMixin -> initLifecycle
// 子组件创建时,父组件已经存在了 vm.$parent = parent vm.$root = parent ? parent.$root : vm vm.$children = [] vm.$refs = {}
vm.$slots = resolveSlots(options._renderChildren, renderContext) vm.$scopedSlots = emptyObject vm.$createElement = (a, b, c, d) => createElement(vm, a, b, c, d, true)
initMixin -> initInjections
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) } }
initMixin -> initProvide
stateMixin
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
lifecycleMixin
renderMixin
数据响应式
initData() 方法
proxy(vm, `_data`, key) observe(data, true /* asRootData */)
srccoreobserverindex.js
ob = new Observer(value) return ob
在Observer的构造函数中:根据data中是Object还是Array,进行不同的响应式处理
constructor (value: any) { this.value = value this.dep = new Dep() this.vmCount = 0 def(value, '__ob__', this) // 判断当前value是数组还是Object if (Array.isArray(value)) { if (hasProto) { protoAugment(value, arrayMethods) } else { copyAugment(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]) } }
当数据中是数组时:arrayMethods
srccoreobserverarray.js
覆盖会修改当前数组的7个方法:'push', 'pop', 'shift', 'unshift', 'splice', 'sort', 'reverse'
methodsToPatch.forEach(function (method) { // cache original 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 }) })
虚拟DOM
优点
实现
// 整个应用程序的挂载点,根组件会执行它,根组件中的任何子组件也会执行它(mountComponent) Vue.prototype.$mount = function ( el?: string | Element, hydrating?: boolean ): Component { el = el && inBrowser ? query(el) : undefined return mountComponent(this, el, hydrating) }
updateComponent = () => { // 首先执行vm._render 返回VNode // 然后VNode作为参数执行update,update就是执行真正的DOM更新 vm._update(vm._render(), hydrating) } // 创建一个组件相关的watcher实例 // $watcher/watch选项会额外创建watcher new Watcher(vm, updateComponent, noop, { before () { if (vm._isMounted && !vm._isDestroyed) { callHook(vm, 'beforeUpdate') } } }, true /* isRenderWatcher */)
_render() srccoreinstance ender.js :创建出虚拟DOM
const { render, _parentVnode } = vm.$options
vnode = render.call(vm._renderProxy, vm.$createElement)
_update() srccoreinstancelifecycle.js
if (!prevVnode) { // initial render // 如果没有老vnode,说明在初始化 vm.$el = vm.__patch__(vm.$el, vnode, hydrating, false /* removeOnly */) } else { // updates // 更新周期直接diff,返回新的dom vm.$el = vm.__patch__(prevVnode, vnode) }
patch() srcplatformsweb untimepatch.js
// the directive module should be applied last, after all // built-in modules have been applied. // 扩展操作:把通用模块和浏览器中特有模块合并 const modules = platformModules.concat(baseModules) // 工厂函数:创建浏览器特有的patch函数,这里主要解决跨平台问题 export const patch: Function = createPatchFunction({ nodeOps, modules })
patch是如何工作的?
/*createPatchFunction的返回值,⼀一个patch函数*/ return function patch(oldVnode, vnode, hydrating, removeOnly, parentElm, refElm) { /*vnode不不存在则删*/ if (isUndef(vnode)) { if (isDef(oldVnode)) invokeDestroyHook(oldVnode) return } let isInitialPatch = false const insertedVnodeQueue = [] if (isUndef(oldVnode)) { /*oldVnode不不存在则创建新节点*/ isInitialPatch = true createElm(vnode, insertedVnodeQueue, parentElm, refElm) } else { /*oldVnode有nodeType,说明传递进来⼀一个DOM元素*/ const isRealElement = isDef(oldVnode.nodeType) if (!isRealElement && sameVnode(oldVnode, vnode)) { /*是组件且是同⼀一个节点的时候打补丁*/ patchVnode(oldVnode, vnode, insertedVnodeQueue, removeOnly) } else { /*传递进来oldVnode是dom元素*/ if (isRealElement) { // 将该dom元素清空 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(parentElm, [oldVnode], 0, 0) } else if (isDef(oldVnode.tag)) { /*调⽤用destroy钩⼦子*/ invokeDestroyHook(oldVnode) } } } /*调⽤用insert钩⼦子*/ invokeInsertHook(vnode, insertedVnodeQueue, isInitialPatch) return vnode.elm }
patchVnode
patchVnode具体规则如下:
1、如果新旧VNode都是静态的,同时它们的key相同(代表同一节点),并且新的VNode是clone或者是标记了v-once,那么只需要替换elm以及componentInstance即可。
2、新老节点均有children子节点,则对子节点进行diff操作,调用updateChildren,这个updateChildren也是diff的核心。
3、如果老节点没有子节点而新节点存在子节点,先清空老节点DOM的文本内容,然后为当前DOM节点加入子节点。
4、当新节点没有子节点而老节点有子节点的时候,则移除该DOM节点的所有子节点。
5、当新老节点都无子节点的时候,只是文本的替换。