本文共--字 阅读约--分钟 | 浏览: -- Last Updated: 2021-05-23
在Vuejs中有这样一段代码:
import { initMixin } from './init'
import { stateMixin } from './state'
import { renderMixin } from './render'
import { eventsMixin } from './events'
import { lifecycleMixin } from './lifecycle'
import { warn } from '../util/index'
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)
export default Vue
其中定义了Vue构造函数,然后分别调用了 initMixin
、 stateMixin
、eventsMixin
、lifecycleMixin
和 renderMixin
5个函数,并将Vue构造函数传给这5个函数,这些函数的作用就是向Vue的原型中挂载方法或属性或进行一些初始化操作。
与事件相关的实例方法有四个,vm.$on
、vm.$once
、vm.$off
、vm.$emit
,这四个方法是在 eventsMixin
中挂载到 Vue 构造函数的 prototype
属性中的,其代码如下:
export function eventsMixin(Vue) {
Vue.prototype.$on = function (event, fn) {}
// ...
// 对应的 $once $off $emit
}
监听当前实例上的自定义事件,事件可以由vm.$emit
触发。
// 使用示例 vm.$on(event, callback)
// 参数
// { string | Array<string> } event
// { Function } callback
vm.$on('test', function(msg) {
console.log(msg);
})
vm.$emit('test', 'hi');
// => "hi"
内部实现:
Vue.prototype.$on = function (event, fn) {
const vm = this;
if (Array.isArray(event)) {
for (let i = 0, l = event.length; i < l; i++) {
this.$on(event[i], fn)
}
} else {
((vm._events[event]) || (vm._events[event] = [])).push(fn)
}
}
其中 vm._events
是一个用来对象用来存储事件,在执行 new Vue()
会执行 this._init
方法进行一系列操作,其中就会在Vue实例上创建一个 _events
属性用来存储事件。
vm._events = Object.create(null);
用于移除自定义事件监听器
// 使用示例 vm.$off(event, callback)
// 参数
// { string | Array<string> } event
// { Function } callback
Vue.prototype.$off = function(event, fn) {
const vm = this;
// 未传任何参数,则清空整个事件列表
if (!arguments.length) {
vm._events = Object.create(null)
return vm
}
if (Array.isArray(event)) {
for (let i = 0, l = event.length; i < l; i++) {
this.$off(event[i], fn)
}
return vm
}
const cbs = vm._events[event]
if (!cbs) {
return vm
}
// 只传事件名,只移除该事件的所有监听器
if (arguments.length === 1) {
vm._events[event] = []
return vm
}
if (fn) {
const cbs = vm._events[event]
let cb
let i = cbs.length
while (i--) {
cb = cbs[i]
if (cb === fn || cb.fn === fn) {
cbs.splice(i,1)
break
}
}
}
return vm
}
需要注意的是,在代码中遍历列表是从后向前循环,这样在列表中移除当前位置的监听器时,不会影响列表未遍历到的监听器的位置。如果是前向后遍历,那么当从列表中移除一个监听时,后面的监听器的位置会自动向前移动一个位置,这会导致下一轮循环时跳过一个元素。
用于监听一个自定义事件,但是只触发一次,在第一次触发之后移除监听器。
// 使用示例 vm.$once(event, callback)
// 参数
// { string | Array<string> } event
// { Function } callback
Vue.prototype.$once = function (event, fn) {
const vm = this;
function on () {
vm.$off(event, on)
// 需要注意的是 这里的 arguments 是事件被触发时,调用on的参数列表,而不是 $once的参数列表
fn.apply(vm, arguments)
}
// 需要注意的是 提供给$on注册的fn是我们自定义的拦截器on函数
// 已不是用户调用 $once时提供的fn,如果用户调用 $off(event, fn) 时就移除不了
// 所以这里要把原来用户提供的fn挂载到on上,在$off的时候也就需要判断 cb.fn === fn
on.fn = fn
vm.$on(event, on)
return vm
}
用于触发当前实例上的事件,附加参数都会传递给监听器回调。
// 使用示例 vm.$emit(event, [...args])
// 参数
// { string } event
// { any } args
Vue.prototype.$emit = function (event) {
const vm = this
let cbs = vm._events[event]
if (cbs) {
const args = toArray(arguments, 1)
for (let i = 0, l = cbs.length; i < l; i++) {
try {
cbs[i].apply(vm, args)
} catch {
handleError(e, vm, `event handler for "${event}"`)
}
}
}
return vm
}
与生命周期相关的实例方法有4个,分别是 vm.$mount
、 vm.$forceUpdate
、vm.$nextTick
和 vm.$destroy
。
其中 vm.$forceUpdate
和 vm.destroy
是在 lifecycleMixin
中挂载到Vue.prototype
上的,vm.$nextTick
是在 renderMixin
中挂载到Vue.prototype
上的。而 vm.$mount
是在跨平台的代码中挂载到Vue.prototype
上的。
用于迫使Vuejs实例重新渲染。
Vue.prototype.$forceUpdate = function () {
const vm = this;
if (vm._watcher) {
vm._watcher.update()
}
}
vm._watcher
就是 Vuejs 实例的Watcher,所以只需要主动执行实例Watcher的update方法,就可以让实例重新渲染。
vm.$destroy
的作用是完全销毁一个实例,它会清理该实例与其他实例的连接,并解绑全部指令及监听器,同时会触发 beforeDestroy
和 destroyed
的钩子函数。
Vue.prototype.$destroy = function () {
const vm = this
if (vm._isBeingDestroyed) { // 正在被销毁,不需要重复执行
return
}
callHook(vm, 'beforeDestroy')
vm._isBeingDestroyed = true
// 删除自己与父级之间的连接
const parent = vm.$parent
if (parent && !parent._isBeingDestroyed && !vm.$options.abstract) { // 存在父组件且父组件没有被销毁且自身不是抽象组件
remove(parent.$children, vm)
}
// 从watcher监听的所有状态的依赖列表中移除watcher
if (vm._watcher) {
vm._watcher.teardown()
}
let i = vm._watchers.length
while (i--) {
vm._watchers[i].teardown()
}
vm._isDestroyed = true
// 在vnode树上触发destroy钩子函数解绑指令
vm.__patch__(vm._vnode, null)
// 触发destroyed钩子函数
callHook(vm, 'destroyed')
// 移除当前实例的所有事件监听
vm.$off()
}
function remove(arr, item) {
if (arr.length) {
const index = arr.indexOf(item)
if (index > 1) {
return arr.splice(index, 1)
}
}
}
$nextTick
接受一个回调函数作为参数,它的作用是将回调延迟到下次DOM更新周期之后执行,与全局的Vue.nextTick
一样,不同的是回调的this自动绑定到调用它的实例上,如果没有提供回调且在支持Promise环境中,则返回一个Promise。
在Vuejs中,当状态发生变化时,watcher 会得到通知,然后触发虚拟DOM的渲染流程,而Watcher触发渲染的这个操作并不是同步的,而是异步的。Vuejs中有一个队列,每当需要渲染时会将Watcher推送到这个队列中,在下一次事件循环中再让Watcher触发渲染的流程。
为什么Vuejs使用异步更新队列?
当状态发生变化,变化侦测的通知只发送到组件级别,组件内用到所有的状态,当组件中多个状态发生变化的时候会通知的会是同一个Watcher(组件级别的Watcher),如果在一个事件循环中有多个数据发生了变化,那么组件Watcher就会收到多份通知,从而进行多次渲染,而事实上,并不需要渲染多次,只需要等所有的状态都修改完毕后,一次性将整个组件的DOM渲染到最新即可。
因此会将收到的Watcher缓存起来,并且在添加到队列之前检查其中是否已经存在相同的Watcher,只有不存在时,才将Watcher实例添加到队列中,然后在下一次事件循环中,Vuejs会让队列中的Watcher触发渲染流程并清空队列,这样就可以保证即便在同一事件循环中有两个状态发生改变,Watcher最后也只执行一次渲染流程。
因此当我们想要获取更新之后的DOM时,就需要使用到$nextTick
,需要注意顺序问题,因为不论是更新DOM的回调还是调用vm.$nextTick
来获取更新后的DOM,都是向微任务队列中添加任务,所以哪个任务先添加到队列中,就先执行哪个任务。
new Vue({
// ...
template: `
<div>
<p id='msg'>{{ msg }}</p>
<p id='text'>{{ text }}</p>
</div>
`,
data() {
return {
msg: '',
text: '',
}
},
methods: {
handler() {
this.$nextTick(() => { // 回调被添加到队列中 1
// DOM未更新
console.log(document.querySelector('#msg').innerText) // ''
})
this.msg = 'hello' // 改变状态 watcher添加到队列中 2
this.$nextTick(() => { // 回调被添加到队列中 3
// DOM更新了
console.log(document.querySelector('#msg').innerText) // hello
})
// 三个微任务的执行顺序就是 1、2、3
// 所以应该是先改变值,再使用nextTick注册回调
// 如果顺序反了,就会先执行nextTick回调,再执行watcher异步渲染DOM
// 就不是期望的DOM更新时了
// 但是可以使用setTimeout将获取更新ODM后的处理逻辑添加到下一个宏任务队列中
setTimeout(() => {
// DOM更新了
console.log(document.querySelector('#text').innerText) // 你好
}, 0);
this.text = '你好'
}
},
})
vm.$nextTick
和 Vue.nextTick
是相同的,所以 nextTick 的具体实现并不是在Vue原型上的 $nextTick
方法中,而是抽象成了 nextTick 方法供两个方法共用。
import { nextTick } from '../util/index'
Vue.prototype.$nextTick = function(fn) {
return nextTick(fn, this)
}
// 需要注意的是,在一轮事件循环中,vm.$nextTick 只会向任务队列添加一个任务
// 多次使用vm.$nextTick只会将回调添加到回调列表中缓存起来
// 当任务触发时 依次执行列表中的所有回调并清空列表
在Vuejs2.4版本之前,nextTick
方法在任何地方都使用微任务,但是微任务优先级太高,在某些场景可能会出现问题。
所以在Vuejs2.5版本中,采用了宏任务和微任务结合使用的方式,可强制采用宏任务。
可是,这样依然存在一些问题,比如状态在重绘之前被改变时会产生一些微妙的问题,在事件处理程序中使用宏任务也会导致一些奇怪的问题,所以在之后的版本中,依旧优先采用微任务的方式,只有在微任务不支持的情况下,才使用宏任务。
import { noop } from 'shared/util' // noop就是一个空函数
import { handleError } from './error'
import { isIE, isIOS, isNative } from './env'
export let isUsingMicroTask = false
const callbacks = []
let pending = false
function flushCallbacks () {
pending = false
const copies = callbacks.slice(0) // 复制
callbacks.length = 0 // 清空
for (let i = 0; i < copies.length; i++) {
copies[i]() // 调用
}
}
let timerFunc
if (typeof Promise !== 'undefined' && isNative(Promise)) {
const p = Promise.resolve()
timerFunc = () => {
p.then(flushCallbacks)
if (isIOS) setTimeout(noop)
}
isUsingMicroTask = true
} else if (!isIE && typeof MutationObserver !== 'undefined' && (
isNative(MutationObserver) ||
MutationObserver.toString() === '[object MutationObserverConstructor]'
)) {
let counter = 1
const observer = new MutationObserver(flushCallbacks)
const textNode = document.createTextNode(String(counter))
observer.observe(textNode, {
characterData: true
})
timerFunc = () => {
counter = (counter + 1) % 2
textNode.data = String(counter)
}
isUsingMicroTask = true
} else if (typeof setImmediate !== 'undefined' && isNative(setImmediate)) {
timerFunc = () => {
setImmediate(flushCallbacks)
}
} else {
timerFunc = () => {
setTimeout(flushCallbacks, 0)
}
}
export function nextTick (cb?: Function, ctx?: Object) {
let _resolve
callbacks.push(() => {
if (cb) {
try {
cb.call(ctx)
} catch (e) {
handleError(e, ctx, 'nextTick')
}
} else if (_resolve) {
_resolve(ctx)
}
})
// timerFunc的作用就是在下一次事件循环时执行flushCallbacks清空掉事件队列(循环执行)
// 初始pending为false,添加第一个任务后,pending为true
// 然后调用timerFunc准备在下次循环执行flushCallbacks
// 如果在本次循环中,多次调用 nextTick,此时 pending还为true只会进行队列的push
// 只有 flushCallbacks在下次循环前被执行完,队列被清空后,pending才置为false
// 等待下一个循环的第一个任务被添加时,又置为 true ...
if (!pending) {
pending = true
timerFunc()
}
// 如果没有传值回调,且环境支持Promise则返回一个Promise
// example:
// this.$nextTick().then(() => console.log('DOM更新了'))
if (!cb && typeof Promise !== 'undefined') {
return new Promise(resolve => {
_resolve = resolve
})
}
}
如果在实例化Vuejs时设置了el
选项,会自动把Vuejs实例挂载到DOM元素上。如果在实例化的时候没有设置el
选项,则它处于”未挂载“状态,没有关联的DOM,这时候使用vm.$mount
手动挂载一个未挂载的实例。
如果没有提供参数,模板将会被渲染为文档之外的元素,并且使用原生DOM的API把它插入文档中,这个方法返回实例自身,因此可以链式调用其他实例方法。
参考 官方文档生命周期图示 可以知道,template 选项的优先级最高,其次是 el 选项。
// 用法 vm.$mount([elementOrSelector]) 接受一个DOM元素或者一个元素选择器作为参数(挂载对象)
var MyComponent = Vue.extend({
template: '<div>Hello</div>'
})
new MyComponent().$mount('#app') // 挂载到 #app上
// 或者 不提供参数,返回一个类fragment,手动插入
var component = new MyComponent().$mount()
document.getElementById('app').appendChild(component.$el)
之前提到过,完整版和只包含运行时版本之间的差异在于是否有编译器,而是否有编译器的差异主要在于 vm.$mount
方法的表现形式。
在完整的构建版本中,vm.$mount
会首先检查 template
或 el
选项所提供的模板是否已经转换成渲染函数 render
,如果没有,则立即进入编译过程,将模板编译成渲染函数,完成之后再进入挂载与渲染的流程中。
在只包含运行时的版本中,vm.$mount
主要就是负责“挂载”,没有编译步骤,它会默认实例上已经存在渲染函数,如果不存在,则会设置一个执行时会返回一个空节点的VNode渲染函数,以保证执行时不会因为函数不存在而报错。
同时,开发环境下Vuejs会触发警告,提示我们当前使用的是只包含运行时版本,会让我们提供渲染函数,或者去使用完整的构建版本。
完整的构建版本的vm.$mount
的实现代码如下:
// 函数劫持
// 使用mount存储原始$mount
// 然后在原始只支持挂载的功能上新增一些其他功能,这个新方法再调用mount
const mount = Vue.prototype.$mount // 只包含运行时的版本的mount
Vue.prototype.$mount = function(el) {
el = el && query(el)
// 是否存在渲染函数
const options = this.$options
if (!options.render) { // 1、是否存在render函数
// 获取模板
let template = options.template
if (template) { // 2、template选项是否存在
if (typeof template === 'string') { // 2.1、template 是不是字符串
if (template.charAt(0) === '#') { // 2.1.1、template选项是一个id选择器
template = idToTemplate(template)
}
// 2.1.2 如果 template 存在,且是字符串,且不是以#开头,直接走第4步流程
} else if (template.nodeType) { // 2.2、template选项是一个DOM节点
template = template.innerHTML
} else { // 2.3 都不是,抛出警告
if (process.env.NODE_ENV !== 'production') {
warn('invalid template option:' + template, this)
}
return this
}
} else if (el) { // 3、是否传递了有效的el参数
template = getOuterHTML(el)
}
}
// 4、将模板编译成渲染函数并赋值给 options.render
if (template) {
const { render } = compileToFunctions(template, { /* ... */ }, this)
options.render = render
}
return mount.call(this, el)
}
function idToTemplate() {
const el = query(id)
return el && el.innerHTML
}
function query(el) {
if (typeof el === 'string') {
const selected = document.querySelector(el)
if (!selected) {
return document.createElement('div')
}
return selected
} else {
return el
}
}
function getOuterHTML(el) {
if (el.outerHTML) {
return el.outerHTML
} else {
const container = document.createElement('div')
container.appendChild(el.cloneNode(true))
return container.innerHTML
}
}
将模板编译成代码字符串并将其转换成渲染函数的过程是在compileToFunctions
函数中实现的,该函数的内部实现如下:
function compileToFunctions(template, options, vm) {
options = extend({}, options) // 混合,让options称为可选参数
// 检查缓存 是否存在编译后的模板
const key = options.delimiters ? String(options.delimiters) + template : template
if (cache[key]) {
return cache[key]
}
// 编译
const compiled = compile(template, options) // 前面提到的将模板编译成代码字符串
// 将代码字符串转换为函数
const res = {}
res.render = createFunction(compiled.render)
return (cache[key] = res)
}
function createFunction(code) {
return new Function(code)
}
2、只包含运行时版本的的vm.$mount
只负责挂载,包含了 vm.$mount
方法的核心功能。
Vue.prototype.$mount = function (el) {
el = el && inBrowser ? query(el) : undefined
return mountComponent(this, el)
}
function mountComponent(vm, el) {
if (!vm.$options.render) {
vm.$options.render = createEmptyVNode
if (process.env.NODE_ENV !== 'production') {
// 发出警告
}
}
// 触发hook
callHook(vm, 'beforeMount')
// 挂载
vm._watcher = new Watcher(vm, () => {
vm._update(vm._render())
}, noop)
// 触发hook
callHook(vm, 'mounted')
return vm
}
_update
调用虚拟DOM中的patch
方法来执行节点的对比与渲染操作,_render
的作用是执行渲染函数得到一份最新的vnode节点树。
所以 vm._update(vm._render())
的作用就是将最新的 vnode 节点树和上一次用到的旧vnode进行对比并更新DOM节点,简单的说就是执行了渲染操作。
使用基础Vue构造器创建一个子类,其参数是一个包含组件选项的对象。
// 使用示例
var Profile = Vue.extend({ // data选项必须是函数
template: '<p>{{ firstName }} {{ lastName }} aka {{ alias }}</p>',
data: function() {
return {
firstName: 'jack',
lastName: 'ma',
alias: 'xcjs'
}
}
})
new Profile().$mount('#mount-ele')
全局API与实例方法是不同的,实例API都是挂载在 Vue.prototype
,而全局API都是挂载在 Vue
构造函数上。
Vue.cid = 0
let cid = 1 // 用于缓存的key值 由于相同的选项应该返回相同的子类,所以需要对于返回进行缓存
// 主要做的事情就是将全局的一些选项(Super.xxx)复制到子类上
Vue.extend = function (extendOptions) {
extendOptions = extendOptions || {}
const Super = this
const SuperId = Super.cid
const cacheCtors = extendOptions._Ctor || (extendOptions._Ctor = {})
if (cacheCtors[SuperId]) {
return cacheCtors[SuperId]
}
const name = extendOptions.name || Super.options.name
if (process.env.NODE_ENV !== 'production') {
if (!/^[a-zA-Z][\w-]*$/.test(name)) {
warn('Invalid component name ... ')
}
}
const Sub = function VueComponent(options) {
this._init(options)
}
// 继承
Sub.prototype = Object.create(Super.prototype)
Sub.prototype.constructor = Sub
Sub.cid = cid++ // Sub.cid 还是 1
// 将父类的options选项继承到子类 并将父类保存到子类的Super属性
Sub.options = mergeOptions(Super.options, extendOptions)
Sub['Super'] = Super
// 对于 Props 和 Computer属性,在扩展时在Vue实例上定义代理getter
// 使得访问vm.name代理为 Sub.prototype._props.name
if (Sub.options.props) {
initProps(Sub)
}
if (Sub.options.computed) {
initComputed(Sub)
}
// 复制父类中存在的方法
Sub.extend = Super.extend
Sub.mixin = Super.mixin
Sub.use = Super.use
// ASSETS_TYPES = ['component', 'directive', 'filter']
ASSETS_TYPES.forEach(function(type) {
Sub[type] = Super[type]
})
if (name) {
Sub.options.component[name] = Sub
}
// 新增属性
Sub.superOptions = Super.options
Sub.extendOptions = extendOptions
Sub.sealedOptions = extend({}, Sub.options)
cacheCtors[SuperId] = Sub // 使用父类的id作为缓存的key
return Sub
}
function initProps (Comp) {
const props = Comp.options.props
for (const key in props) {
proxy(Comp.prototype, `_props`, key)
}
}
function initComputed (Comp) {
const computed = Comp.options.computed
for (const key in computed) {
defineComputed(Comp.prototype, key, computed[key])
}
}
在下一次DOM更新结束之后执行延迟回调,修改数据之后立即使用这个方法获取更新后的DOM
与之前提到的 vm.$nextTick一样都是使用 nextTick 函数
// Vue.nextTick([callback, context])
import { nextTick } from '../util/index'
Vue.nextTick = nextTick
设置对象的属性,如果对象是响应式的,确保属性创建后也是响应式的,同时触发视图更新,这个方法主要用于避开Vue不能检测属性被添加的限制。与 vm.$set 实现原理一致。
删除对象的属性,如果对象是响应式的,确保删除能够触发视图更新,这个方法主要用于避开Vue不能检测属性被删除的限制。与 vm.$delete 实现原理一致。
注册或获取全局指令,注意是注册和获取,而不是让指令生效,注册和获取需要做的事是将指令保存在某个位置,而让指令生效是从某个位置拿出来并执行它。
// Vue.directive(id, [definition])
// { String } id
// { Function | Object } definition
Vue.options = Object.create(null)
Vue.options['directives'] = Object.create(null) // 保存指令的位置
Vue.directive = function(id, definition) {
if (!definition) { // 获取
return this.options['directives'][id]
} else { // 注册
if (typeof definition === 'function') {
definition = { bind: definition, update: definition }
}
this.options['directives'][id] = definition
return definition
}
}
同理,Vue.filter
用来注册或获取全局过滤器,Vue.component
用来注册或获取全局组件,所以可以封装到一起。
export function initAssetRegisters (Vue: GlobalAPI) {
// ASSETS_TYPES = ['component', 'directive', 'filter']
ASSET_TYPES.forEach(type => {
Vue[type] = function (id, definition) {
if (!definition) {
return this.options[type + 's'][id]
} else {
// 无效name处理
if (process.env.NODE_ENV !== 'production' && type === 'component') {
validateComponentName(id)
}
if (type === 'component' && isPlainObject(definition)) { // isPlainObject 判断是不是对象 [object Object]
definition.name = definition.name || id // 如果注册组件时没有设置组件名,则自动使用给定的id设置组件的名称
definition = this.options._base.extend(definition) // Component其实是一个构造函数,所以需要将传入的 definition 处理成构造器,将其变成Vue的子类
}
if (type === 'directive' && typeof definition === 'function') {
definition = { bind: definition, update: definition }
}
this.options[type + 's'][id] = definition
return definition
}
}
})
}
安装插件,如果插件是一个对象,必须提供 install
方法。如果插件是一个对象,它会被当做 install
方法,调用 install
方法会将 Vue
作为参数传入,插件只能被安装一次,多次调用没有意义。
Vue.use = function (plugin) {
const installedPlugins = (this._installedPlugins || (this._installedPlugins = []))
if (installedPlugins.indexOf(plugin) > -1) { // 只能安装一次
return this
}
const args = toArray(arguments, 1) // 除plugin外的其他参数
args.unshift(this) // 将this即Vue补充到参数列表中
if (typeof plugin.install === 'function') {
plugin.install.apply(plugin, args)
} else if (typeof plugin === 'function') {
plugin.apply(null, args)
}
installedPlugins.push(plugin)
return this
}
全局注册一个混入(mixin),影响注册之后的每个Vue.js实例(组件也是实例),插件作者可以使用混入向组件注入自定义行为,如监听生命周期钩子。
import { mergeOptions } from '../util/index'
export function initMixin(Vue) {
Vue.mixin = function(mixin) {
// this.options 就是 Vue.options 之后创建的每个实例都会用到该属性
this.options = mergeOptions(this.options, this)
return this
}
}
编译模板字符串并返回包含渲染函数的对象,只在完整版中才有效。
var res = Vue.compile(`<div><span>{{ msg }}</span></div>`)
new Vue({
data: {
msg: 'hello'
},
render: res.render
})
其实现方式就是之前提到过的 compileToFunctions:
Vue.compile = compileToFunctions;
提供字符串式的Vuejs安装版本号。
// 使用场景
var version = Number(Vue.version.split('.')[0])
if (version === 2) {
// Vuejs v2.x.x
} else if (version === 1) {
// Vuejs v1.x.x
} else {
// ...
}
具体的实现步骤是:Vuejs在构建文件的配置中定义了 __VERSION__
常量,使用 rollup-plugin-replace
插件在构建的过程中将代码的常量 __VERSION__
替换成 pkg.version
。
Vue.version = '__VERSION__'
// 构建之后
Vue.version = '2.5.2'