# fed-e-task-vue **Repository Path**: frontend_site/fed-e-task-vue ## Basic Information - **Project Name**: fed-e-task-vue - **Description**: learn vue,通读Vue2.x源码,并做注释 - **Primary Language**: JavaScript - **License**: MIT - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 1 - **Created**: 2020-07-20 - **Last Updated**: 2021-08-10 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # Vue Vue2.x,获取源码git地址:https://github.com/vuejs/vue ## 1. 调试准备 1. 在vue-dev的package.json文件添加 --sourcemap,用于生成.map文件 ```javascript "dev": "rollup -w -c scripts/config.js --environment TARGET:web-full-dev --sourcemap", // "dev": "rollup -w -c scripts/config.js --sourcemap --environment TARGET:web-full-dev" ``` 2. 生成vue.js和vue.js.map文件 ```shell yarn install yarn run dev ``` 3. 编辑器配置 ```javascript { "javascript.validate.enable": false, } ``` 4. 辅助工具 ```javascript https://vue-next-template-explorer.netlify.app/ https://template-explorer.vuejs.org/ https://astexplorer.net/ recast https://www.npmjs.com/package/recast simple-html-parser https://www.npmjs.com/package/simple-html-parser ``` 5. 目录结构 ```javascript src ├── compiler ├── core ├── platforms │ ├── web ``` ## 2. 代码说明 1. src\core\instance\index.js ```javascript // 定义Vue函数,即定义Vue的构造函数 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') } /** * 在new Vue({})时 * 即调用_init函数 * 该函数被定义时间 * initMixin(Vue) */ this._init(options) } /** * 注册_init函数: * this._init(options) * 在new Vue({})时调用,初始化vm实例 * 处理实例对象数据 * src\core\instance\init.js */ initMixin(Vue) /** * 使用Object.defineProperty * 特殊处理 * 在Vue.prototype上挂载以下函数 * $data、$props * $set、$watch、$delete */ stateMixin(Vue) /** * 混入与事件相关的函数 * $on、$off、$once、$emit * 采用发布-订阅的设计模式 */ eventsMixin(Vue) /** * 混入与生命周期相关的函数 * _update、$forceUpdate、$destroy * _update函数中调用vm.__patch__函数 * 即进行diff算法,比较新旧vdom节点, * 并渲染成真实dom元素 * 在渲染视图时调用,包括首次渲染、数据更新时 */ lifecycleMixin(Vue) /** * 通过installRenderHelpers(Vue.prototype)函数: * 在Vue.prototype上定义许多编译时使用的辅助函数 * 混入与渲染相关的函数 * $nextTick、与Vue.nextTick对应 * _render: * 函数体部分代码: * const { render, _parentVnode } = vm.$options * vnode = render.call(vm._renderProxy, vm.$createElement) * 返回值:vnode * vm.$createElement,即传入的h函数 * 在调用this._init(options)时给vm实例上添加的$createElement函数 * 即./init.js中调用initRender(vm)时添加 */ renderMixin(Vue) /** * 以上函数,在Vue.prototype上挂在函数,即定义Vue实例方法 */ export default Vue ``` 2. src\core\index.js ```javascript /** * 在Vue上挂载静态方法 * 例如以下方法: * Vue.set、Vue.del、Vue.nextTick、Vue.observable * Vue.extend、Vue.mixin、Vue.use、 * Vue.component、Vue.directive、Vue.filter * 在Vue.options.components上挂载组件: keep-alive等 */ initGlobalAPI(Vue) // Object.defineProperty Object.defineProperty(Vue.prototype, '$isServer', { get: isServerRendering }) Object.defineProperty(Vue.prototype, '$ssrContext', { get () { /* istanbul ignore next */ return this.$vnode && this.$vnode.ssrContext } }) // expose FunctionalRenderContext for ssr runtime helper installation Object.defineProperty(Vue, 'FunctionalRenderContext', { value: FunctionalRenderContext }) ``` 3. src\platforms\web\runtime\index.js ```javascript // \platforms\目录,代表与平台相关的代码定义,即与web平台相关 // 该文件里主要定义与平台相关的方法 import { mountComponent } from 'core/instance/lifecycle' // 在Vue.config上挂载与平台相关的方法 Vue.config.mustUseProp = mustUseProp Vue.config.isReservedTag = isReservedTag Vue.config.isReservedAttr = isReservedAttr Vue.config.getTagNamespace = getTagNamespace Vue.config.isUnknownElement = isUnknownElement // 定义与web平台相关的自有指令、组件 extend(Vue.options.directives, platformDirectives) // 注册和平台相关的全局指令:v-model、v-show extend(Vue.options.components, platformComponents) // 注册和平台相关的全局组件:v-transition、v-transition-group // --install platform patch function Vue.prototype.__patch__ = inBrowser ? patch : noop // 把虚拟DOM转换成真实 DOM /** * public mount method * 定义$mount函数,挂载方法设置,挂载DOM到页面 * $mount函数是与平台相关函数 * 在this._init(options)中调用: * vm.$mount(vm.$options.el) * _init函数在new Vue({})时调用, * 使用initMixin函数挂载到Vue.prototype上 */ Vue.prototype.$mount = function ( el?: string | Element, hydrating?: boolean ): Component { el = el && inBrowser ? query(el) : undefined /** * new Watcher(vm, updateComponent, noop, {}) * updateComponent是关键函数 * updateComponent = () => {vm._update(vm._render(), hydrating)} * 通知视图更新 * 在lifecycleMixin(Vue)时定义vm._update函数 * 在renderMixin(Vue)时定义vm._render函数 * vm.$el = vm.__patch__(prevVnode, vnode) */ return mountComponent(this, el, hydrating) } ``` 4. src\platforms\web\entry-runtime-with-compiler.js ```javascript // 从该文件入口依次往上查找,引用关系: 4=>3=>2=>1 import { compileToFunctions } from './compiler/index' // 重写$mount函数,判断是否传入render属性; 若没有传入,通过template生成render属性并在options上挂载 const mount = Vue.prototype.$mount Vue.prototype.$mount = function ( el?: string | Element, hydrating?: boolean ): Component { el = el && query(el) const options = this.$options // resolve template/el and convert to render function if (!options.render) { let template = options.template if (template) { // 根据是否定义render属性,执行相应逻辑 } else if (el) { template = getOuterHTML(el) // 通过传入el,将dom转化为template } if (template) { /** * 关键步骤、即获取render属性,并在options上挂载 * 在mount.call(this, el, hydrating)会使用options.render * 在new Watcher(vm, updateComponent, noop, {})时调用,获取vnode节点 * 查看: app.$options.render app.$options.staticRenderFns * app.$options.staticRenderFns存放静态根节点的生成函数, * 在app.$options.render中使用下标获取相应函数,进行生成相应节点,如: _m(0) * 参考src\core\instance\render-helpers\render-static.js -- this.$options.staticRenderFns[index] * 使用缓存,减少静态根节点对应的vdom的创建次数 * 获取vnode: app.$options.render.call(app) */ const { render, staticRenderFns } = compileToFunctions(template, { outputSourceRange: process.env.NODE_ENV !== 'production', shouldDecodeNewlines, shouldDecodeNewlinesForHref, delimiters: options.delimiters, comments: options.comments }, this) options.render = render } } return mount.call(this, el, hydrating) // 执行web\runtime\index.js里定义的mount函数 } /** * 编译函数: * compileToFunctions * 返回值: render, staticRenderFns * 挂载Vue.compile函数, * 传递一个HTML字符串返回render函数 */ Vue.compile = compileToFunctions export default Vue ``` ## 3. 响应式 1. src\core\instance\state.js ```javascript export function initState (vm: Component) { /** * vm是Vue实例对象,可以通过自定义watch、computed观察vm._watchers数量 */ 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) { // observe data - 响应式数据处理 observe(data, true /* asRootData */) } ``` 2. src\core\observer\index.js ```javascript export function observe (value: any, asRootData: ?boolean): Observer | void { if (!isObject(value) || value instanceof VNode) { return } let ob: Observer | void 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++ } return ob } export class Observer { value: any; dep: Dep; vmCount: number; // number of vms that have this object as root $data constructor (value: any) { this.value = value // 在数据响应式处理前,已调用initRender(vm)函数,创建两个Dep实例,即id:0、1的两个实例 this.dep = new Dep() this.vmCount = 0 def(value, '__ob__', this) // 挂载__ob__属性 if (Array.isArray(value)) { if (hasProto) { protoAugment(value, arrayMethods) } else { copyAugment(value, arrayMethods, arrayKeys) } this.observeArray(value) } else { this.walk(value) } } } export function defineReactive ( obj: Object, key: string, val: any, customSetter?: ?Function, shallow?: boolean ) { 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] } 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 /* eslint-disable no-self-compare */ if (newVal === value || (newVal !== newVal && value !== value)) { return } /* eslint-enable no-self-compare */ if (process.env.NODE_ENV !== 'production' && customSetter) { customSetter() } // #7981: for accessor properties without setter if (getter && !setter) return if (setter) { setter.call(obj, newVal) } else { val = newVal } childOb = !shallow && observe(newVal) dep.notify() } }) } export default class Dep { depend () { if (Dep.target) { Dep.target.addDep(this) } } } // dep.notify() => watcher.update() export default class Watcher { update () { /* istanbul ignore else */ if (this.lazy) { this.dirty = true } else if (this.sync) { this.run() } else { queueWatcher(this) } } } ``` ## 4. 模板编译 1. src\platforms\web\entry-runtime-with-compiler.js ```javascript /** * 关键步骤、即获取render属性,并在options上挂载 * 在mount.call(this, el, hydrating)会使用options.render * 在new Watcher(vm, updateComponent, noop, {})时调用,获取vnode节点 */ const { render, staticRenderFns } = compileToFunctions(template, { outputSourceRange: process.env.NODE_ENV !== 'production', shouldDecodeNewlines, shouldDecodeNewlinesForHref, delimiters: options.delimiters, comments: options.comments }, this) options.render = render /** * src\platforms\web\runtime\index.js * 执行Vue.prototype.$mount函数 */ return mount.call(this, el, hydrating) ``` 2. src\compiler\to-function.js ```javascript // 解析compileToFunctions函数,返回render, staticRenderFns // src\compiler\to-function.js /** * compile * 将模板字符串编译为对象 * 包含{ render, staticRenderFns } */ const compiled = compile(template, options) // src\compiler\create-compiler.js // 编译核心函数 const compiled = baseCompile(template.trim(), finalOptions) // src\compiler\index.js export const createCompiler = createCompilerCreator(function baseCompile ( template: string, options: CompilerOptions ): CompiledResult { // 编译template为ast抽象语法树 const ast = parse(template.trim(), options) if (options.optimize !== false) { /** * 优化ast抽象语法树 * 1. 标记静态节点: 不包含动态内容的节点 * 2. 标记静态根节点: 不包含动态内容且含义子标签的节点 */ optimize(ast, options) } /** * 生成字符串形式的render函数 * const code = ast ? genElement(ast, state) : '_c("div")' */ const code = generate(ast, options) return { ast, render: code.render, staticRenderFns: code.staticRenderFns } }) // src\compiler\to-function.js // 将字符串形式的代码转化成函数 res.render = createFunction(compiled.render, fnGenErrors) res.staticRenderFns = compiled.staticRenderFns.map(code => { return createFunction(code, fnGenErrors) }) return (cache[key] = res) ``` 3. src\core\instance\lifecycle.js ```javascript /** * vm._render() * 系开发者直接传入,或通过template属性编译生成 * 调用该函数可获取vnode节点 * 传入vm._update函数中,用于__patch__函数,比较新旧节点差异 */ updateComponent = () => { vm._update(vm._render(), hydrating) } // vm._render() vnode = render.call(vm._renderProxy, vm.$createElement) // 在调用getter函数时,完成依赖收集处理,以便在调用setter函数时,通知Watcher,调用其update函数更新视图 new Watcher(vm, updateComponent, noop, { before () { if (vm._isMounted && !vm._isDestroyed) { callHook(vm, 'beforeUpdate') } } }, true /* isRenderWatcher */) ``` ## 5. patch 1. src\core\vdom\patch.js ```javascript // patch => createElm => createComponent => createChildren function patch (oldVnode, vnode, hydrating, removeOnly) { if (isUndef(oldVnode)) { /** * empty mount (likely as component), create new root element * 将组件实例化为真实dom节点,进入该判断 * 即.$mount()未传入参数,仅创建组件,没有挂载到页面上 */ isInitialPatch = true createElm(vnode, insertedVnodeQueue) } else { // 新旧节点都存在,进入该判断 const isRealElement = isDef(oldVnode.nodeType) if (!isRealElement && sameVnode(oldVnode, vnode)) { // patch existing root node -- 视图更新操作,执行diff算法 patchVnode(oldVnode, vnode, insertedVnodeQueue, null, null, removeOnly) } else { // 判断是否是真实dom节点,即首次渲染,初始化,创建一个空节点 if (isRealElement) { oldVnode = emptyNodeAt(oldVnode) } const oldElm = oldVnode.elm const parentElm = nodeOps.parentNode(oldElm) createElm( vnode, insertedVnodeQueue, // extremely rare edge case: do not insert if old element is in a // leaving transition. Only happens when combining transition + // keep-alive + HOCs. (#4590) oldElm._leaveCb ? null : parentElm, nodeOps.nextSibling(oldElm) ) } } // 创建节点 function createElm( vnode, insertedVnodeQueue, parentElm, refElm, nested, ownerArray, index ) { // 处理组件的情况 if (createComponent(vnode, insertedVnodeQueue, parentElm, refElm)) { return } if (isDef(tag)) { // 标签节点逻辑 vnode.elm = vnode.ns ? nodeOps.createElementNS(vnode.ns, tag) : nodeOps.createElement(tag, vnode) setScope(vnode) if (__WEEX__) { } else { // 递归调用createElm函数,创建真实节点 createChildren(vnode, children, insertedVnodeQueue) if (isDef(data)) { invokeCreateHooks(vnode, insertedVnodeQueue) } // 子节点创建完毕后,依次插入元素,最后挂载到页面上 insert(parentElm, vnode.elm, refElm) } } else if (isTrue(vnode.isComment)) { // 注释节点逻辑 vnode.elm = nodeOps.createComment(vnode.text) // 插入元素,挂载到页面上 insert(parentElm, vnode.elm, refElm) } else { // 文本节点逻辑 vnode.elm = nodeOps.createTextNode(vnode.text) // 插入元素,挂载到页面上 insert(parentElm, vnode.elm, refElm) } } // createElement => _createElement => vnode = createComponent(Ctor, data, context, children, tag) // 在createComponent函数中通过data.hooks初始化组件,进行挂载、new Watcher操作 function createComponent (vnode, insertedVnodeQueue, parentElm, refElm) { let i = vnode.data if (isDef(i)) { const isReactivated = isDef(vnode.componentInstance) && i.keepAlive if (isDef(i = i.hook) && isDef(i = i.init)) { i(vnode, false /* hydrating */) } if (isDef(vnode.componentInstance)) { initComponent(vnode, insertedVnodeQueue) insert(parentElm, vnode.elm, refElm) if (isTrue(isReactivated)) { reactivateComponent(vnode, insertedVnodeQueue, parentElm, refElm) } return true } } } // 一个组件对应一个Watcher实例,Vue实例对应一个Watcher实例,即Vue2.x中是(n+1)个Watcher实例 // 通过以下vm.$createElement将hooks挂载到组件的data上 vnode = render.call(vm._renderProxy, vm.$createElement) ``` 2. src\core\vdom\patch.js ```javascript function patchVnode( oldVnode, vnode, insertedVnodeQueue, ownerArray, index, removeOnly ) { if (isUndef(vnode.text)) { // 新节点中没有text属性,即对比子节点 if (isDef(oldCh) && isDef(ch)) { // 新旧节点都有子节点,即updateChildren操作 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) { // 新节点中有text属性,更新text属性 nodeOps.setTextContent(elm, vnode.text) } } function updateChildren(parentElm, oldCh, newCh, insertedVnodeQueue, removeOnly) { let oldStartIdx = 0 let newStartIdx = 0 let oldStartVnode = oldCh[0] let oldEndVnode = oldCh[oldEndIdx] let newStartVnode = newCh[0] let newEndVnode = newCh[newEndIdx] while (oldStartIdx <= oldEndIdx && newStartIdx <= newEndIdx) { // 四种主要节点比较 // oldStartVnode-newStartVnode,oldEndVnode-newEndVnode // oldStartVnode-newEndVnode,oldEndVnode-newStartVnode // 以上四种情况都不满足,newStartVnode与oldCh数组一次比较,++newStartIdx if (isUndef(oldKeyToIdx)) oldKeyToIdx = createKeyToOldIdx(oldCh, oldStartIdx, oldEndIdx) idxInOld = isDef(newStartVnode.key) ? oldKeyToIdx[newStartVnode.key] : findIdxInOld(newStartVnode, oldCh, oldStartIdx, oldEndIdx) // 后续操作 } if (oldStartIdx > oldEndIdx) { refElm = isUndef(newCh[newEndIdx + 1]) ? null : newCh[newEndIdx + 1].elm addVnodes(parentElm, refElm, newCh, newStartIdx, newEndIdx, insertedVnodeQueue) } else if (newStartIdx > newEndIdx) { removeVnodes(oldCh, oldStartIdx, oldEndIdx) } } function findIdxInOld(node, oldCh, start, end) { for (let i = start; i < end; i++) { const c = oldCh[i] if (isDef(c) && sameVnode(node, c)) return i } } ``` ## 6. 代码流程 1. 数据响应式处理 ```javascript new Vue({}) this._init(options) // src\core\instance\init.js部分代码 /** * 依次对传入参数进行初始化处理并挂载到vm上 * props、methods、data、computed、watch * 在该函数中亦对数据进行响应式处理 * 初始化vm._watchers,vm._watchers = [] * src\core\instance\state.js */ initState(vm) /** * 判断vm.$options.el * 进行创建dom节点,最终挂载dom元素操作 * src\platforms\web\entry-runtime-with-compiler.js * 执行web平台下定义的$mount函数 */ if (vm.$options.el) { vm.$mount(vm.$options.el) } ``` 2. 模板编译 ```javascript // src\platforms\web\entry-runtime-with-compiler.js 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 /** * src\platforms\web\runtime\index.js * 执行Vue.prototype.$mount函数 */ return mount.call(this, el, hydrating) // compileToFunction函数部分代码 function baseCompile ( template: string, options: CompilerOptions ): CompiledResult { // 编译template为ast抽象语法树 const ast = parse(template.trim(), options) if (options.optimize !== false) { /** * 优化ast抽象语法树 * 1. 标记静态节点: 不包含动态内容的节点 * 2. 标记静态根节点: 不包含动态内容且含义子标签的节点 */ optimize(ast, options) } /** * 生成字符串形式的render函数 * const code = ast ? genElement(ast, state) : '_c("div")' */ const code = generate(ast, options) return { ast, render: code.render, staticRenderFns: code.staticRenderFns } } ``` 3. new Watcher ```javascript // src\core\instance\lifecycle.js // return mountComponent(this, el, hydrating) updateComponent = () => { vm._update(vm._render(), hydrating) } // 创建Vue实例与Vue组件时,均会实例化Watcher new Watcher(vm, updateComponent, noop, { before() { if (vm._isMounted && !vm._isDestroyed) { callHook(vm, 'beforeUpdate') } } }, true /* isRenderWatcher */) ``` 4. 页面渲染 ```javascript // vm._update(vm._render(), hydrating) if (!prevVnode) { // initial render vm.$el = vm.__patch__(vm.$el, vnode, hydrating, false /* removeOnly */) } else { // updates vm.$el = vm.__patch__(prevVnode, vnode) } ```