赞
踩
vue实例挂载的实现,也就是执行 vm.$mount 的方法
在 Runtime + Compiler 版本,入口文件是: src/platform/web/entry-runtime-with-compiler.js
$mount 方法也是在这个文件中被定义的
const mount = Vue.prototype.$mount Vue.prototype.$mount = function ( el?: string | Element, hydrating?: boolean ): Component { // 首先对于传入的这个 el 参数,做了一个处理,可以看到 el 参数,可以是个字符串,也可以是个 Element // 这里调用了query方法,query方法实际上就是原生的方法,有则document.querySelector,无则,document.createElement 返回一个div 等处理 el = el && query(el) // 拿到这个 el 以后,这里就已经被转化成了这个 dom 对象,然后它又做了一个简单的判断 // 也就是说我的 el 如果是 body 或者是 html 标签的话,它就会报一个错 // 就是 vue 不可以直接挂载到这个 body 或者 html 上,因为它是会覆盖的,你挂载的话,你会把整个body覆盖,那整个HTML文档不对了 // 所以说这里在开发环境下,就报了这个警告 /* istanbul ignore if */ if (el === document.body || el === document.documentElement) { process.env.NODE_ENV !== 'production' && warn( `Do not mount Vue to <html> or <body> - mount to normal elements instead.` ) return this } // 拿到这个options。判断有没有定义render方法,因为平时开发的过程中,代码都是脚手架生成,会有一个 render const options = this.$options // resolve template/el and convert to render function if (!options.render) { // 再次判断你有没有写template,一般模板默认是有index.html的,在组件内部也可以使用 template, 有则进行处理 let template = options.template if (template) { if (typeof template === 'string') { if (template.charAt(0) === '#') { template = idToTemplate(template) /* istanbul ignore if */ if (process.env.NODE_ENV !== 'production' && !template) { warn( `Template element not found or is empty: ${options.template}`, this ) } } } 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) { // 如果没有 templete 则执行 getOuterHTML 拿到html template = getOuterHTML(el) } if (template) { /* istanbul ignore if */ if (process.env.NODE_ENV !== 'production' && config.performance && mark) { mark('compile') } // 这一块就是跟编译相关了, 编译的话,它其实是调用这个 compileToFunction // 拿到生成的一个 render 函数,还有 staticRenderFns const { render, staticRenderFns } = compileToFunctions(template, { outputSourceRange: process.env.NODE_ENV !== 'production', shouldDecodeNewlines, shouldDecodeNewlinesForHref, delimiters: options.delimiters, comments: options.comments }, this) // 这个 options.render 会在渲染 vnode 的时候会用到 options.render = render options.staticRenderFns = staticRenderFns /* istanbul ignore if */ if (process.env.NODE_ENV !== 'production' && config.performance && mark) { mark('compile end') measure(`vue ${this._name} compile`, 'compile', 'compile end') } } } // 调用 mount, 这个mount 是 runtimeOnly 时的 $mount return mount.call(this, el, hydrating) }
它首先获得了 Vue.prototype.$mount
方法,用这个 mount 变量缓存起来,然后又重新定义了一遍这个方法
回到最初的定义,在 src/platforms/web/runtime/index.js 中,是最原始的定义
// public mount method Vue.prototype.$mount = function ( el?: string | Element, hydrating?: boolean ): Component { el = el && inBrowser ? query(el) : undefined return mountComponent(this, el, hydrating) } // devtools global hook /* istanbul ignore next */ if (inBrowser) { setTimeout(() => { if (config.devtools) { if (devtools) { devtools.emit('init', Vue) } else if ( process.env.NODE_ENV !== 'production' && process.env.NODE_ENV !== 'test' ) { console[console.info ? 'info' : 'log']( 'Download the Vue Devtools extension for a better development experience:\n' + 'https://github.com/vuejs/vue-devtools' ) } } if (process.env.NODE_ENV !== 'production' && process.env.NODE_ENV !== 'test' && config.productionTip !== false && typeof console !== 'undefined' ) { console[console.info ? 'info' : 'log']( `You are running Vue in development mode.\n` + `Make sure to turn on production mode when deploying for production.\n` + `See more tips at https://vuejs.org/guide/deployment.html` ) } }, 0) }
在我们的这个入口, 为什么会重新定义一遍 $mount?
实际上, 上面这块最原始的代码是给 RuntimeOnly 版本复用用的一个函数
在 Runtime + Compiler 版本,在 src/core/instance/init.js 的 initMixin 中
执行 vm.$mount 时候,实际上调的就是 src/platform/web/entry-runtime-with-compiler.js 这个入口文件
中的 Vue.prototype.$mount 这个函数,现在回到入口文件中的代码中,查看相关代码上的注释
在执行了入口文件中的 .$mount, 最终调用了 RuntimeOnly 的 $mount, 最终执行 mountComponent 方法
而 mountComponent 是在 src/core/instance/lifecycle.js 中定义的
export function mountComponent ( vm: Component, el: ?Element, hydrating?: boolean ): Component { // 首先会把这个 el 的dom 被 vm.$el 缓存起来 vm.$el = el // 判断有没有 render 函数(或没有 template转换来的 render), 没有则创建一个空的 vnode, 并在开发环境警告 if (!vm.$options.render) { vm.$options.render = createEmptyVNode // 这个警告就是写了 template,但是没有使用含有编译的版本,或者两者都没有写 if (process.env.NODE_ENV !== 'production') { /* istanbul ignore if */ if ((vm.$options.template && vm.$options.template.charAt(0) !== '#') || vm.$options.el || el) { warn( 'You are using the runtime-only build of Vue where the template ' + 'compiler is not available. Either pre-compile the templates into ' + 'render functions, or use the compiler-included build.', vm ) } else { warn( 'Failed to mount component: template or render function not defined.', vm ) } } } // 这里先执行 beforeMount 的钩子函数,跳过 callHook(vm, 'beforeMount') let updateComponent // 在dev环境上是更多做了性能埋点相关的处理,当性能比较卡顿时,可以利用这些东西,看文档就行,跳过 /* istanbul ignore if */ if (process.env.NODE_ENV !== 'production' && config.performance && mark) { updateComponent = () => { const name = vm._name const id = vm._uid const startTag = `vue-perf-start:${id}` const endTag = `vue-perf-end:${id}` mark(startTag) const vnode = vm._render() mark(endTag) measure(`vue ${name} render`, startTag, endTag) mark(startTag) vm._update(vnode, hydrating) mark(endTag) measure(`vue ${name} patch`, startTag, endTag) } } else { // 这里才是我们需要关注的 updateComponent = () => { vm._update(vm._render(), hydrating) } } // 这个函数就是调用了 vm._update,第一个参数是通过 vm._render渲染出来一个vnode, 第二个参数理解为 false 就可以了 // 之后,调用 new Watcher 这里实际上是 渲染 watcher // 因为 watcher 这个东西其实是跟响应式原理强相关的一个类 // 它实际上就是一个观察者模式,它其实有很多自定义 watcher,也会有一个叫渲染 watcher // 调用 new Watcher 的的时候,三个参数,第一个vm, 第二个 updateComponent函数, 第三个 noop(空函数), 第四个配置对象,第五个布尔值 // we set this to vm._watcher inside the watcher's constructor // since the watcher's initial patch may call $forceUpdate (e.g. inside child // component's mounted hook), which relies on vm._watcher being already defined new Watcher(vm, updateComponent, noop, { before () { if (vm._isMounted && !vm._isDestroyed) { callHook(vm, 'beforeUpdate') } } }, true /* isRenderWatcher */) // 注意这里第5个参数是 true, 表示是一个渲染watcher hydrating = false // manually mounted instance, call mounted on self // mounted is called for render-created child components in its inserted hook if (vm.$vnode == null) { vm._isMounted = true callHook(vm, 'mounted') } return vm }
/** * A watcher parses an expression, collects dependencies, * and fires callback when the expression value changes. * This is used for both the $watch() api and directives. */ export default class Watcher { vm: Component; expression: string; cb: Function; id: number; deep: boolean; user: boolean; lazy: boolean; sync: boolean; dirty: boolean; active: boolean; deps: Array<Dep>; newDeps: Array<Dep>; depIds: SimpleSet; newDepIds: SimpleSet; before: ?Function; getter: Function; value: any; constructor ( vm: Component, expOrFn: string | Function, cb: Function, options?: ?Object, isRenderWatcher?: boolean // 是否是渲染 watcher ) { this.vm = vm if (isRenderWatcher) { vm._watcher = this } vm._watchers.push(this) // 收集watcher // 这里忽略这个options // 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 } 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() : '' // 注意这里,这里就是调用方传进来的 updateComponent // parse expression for getter if (typeof expOrFn === 'function') { this.getter = expOrFn } else { this.getter = parsePath(expOrFn) if (!this.getter) { this.getter = noop process.env.NODE_ENV !== 'production' && warn( `Failed watching path: "${expOrFn}" ` + 'Watcher only accepts simple dot-delimited paths. ' + 'For full control, use a function instead.', vm ) } } this.value = this.lazy ? undefined : this.get() } // 这里有依赖收集,相关的 /** * Evaluate the getter, and re-collect dependencies. */ get () { pushTarget(this) let value const vm = this.vm try { value = this.getter.call(vm, vm) // 这里会调用 getter, 也就是上层调用方传进来的 updateComponent 方法,就会执行方法内部的 update } catch (e) { if (this.user) { handleError(e, vm, `getter for watcher "${this.expression}"`) } else { throw e } } finally { // "touch" every property so they are all tracked as // dependencies for deep watching if (this.deep) { traverse(value) } popTarget() this.cleanupDeps() } return value } /** * Add a dependency to this directive. */ 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) } } } /** * Clean up for dependency collection. */ cleanupDeps () { let i = this.deps.length while (i--) { const dep = this.deps[i] if (!this.newDepIds.has(dep.id)) { dep.removeSub(this) } } let tmp = this.depIds this.depIds = this.newDepIds this.newDepIds = tmp this.newDepIds.clear() tmp = this.deps this.deps = this.newDeps this.newDeps = tmp this.newDeps.length = 0 } /** * Subscriber interface. * Will be called when a dependency changes. */ update () { /* istanbul ignore else */ if (this.lazy) { this.dirty = true } else if (this.sync) { this.run() } else { queueWatcher(this) } } /** * Scheduler job interface. * Will be called by the scheduler. */ run () { if (this.active) { const value = this.get() if ( value !== this.value || // Deep watchers and watchers on Object/Arrays should fire even // when the value is the same, because the value may // have mutated. isObject(value) || this.deep ) { // set new value const oldValue = this.value this.value = value if (this.user) { const info = `callback for watcher "${this.expression}"` invokeWithErrorHandling(this.cb, this.vm, [value, oldValue], this.vm, info) } else { this.cb.call(this.vm, value, oldValue) } } } } /** * Evaluate the value of the watcher. * This only gets called for lazy watchers. */ evaluate () { this.value = this.get() this.dirty = false } /** * Depend on all deps collected by this watcher. */ depend () { let i = this.deps.length while (i--) { this.deps[i].depend() } } /** * Remove self from all dependencies' subscriber list. */ teardown () { if (this.active) { // remove self from vm's watcher list // this is a somewhat expensive operation so we skip it // if the vm is being destroyed. if (!this.vm._isBeingDestroyed) { remove(this.vm._watchers, this) } let i = this.deps.length while (i--) { this.deps[i].removeSub(this) } this.active = false } } }
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。