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五个函数。这五个函数的作用是向Vue的原型挂载方法。
以initMixin为例
export function initMixin (Vue) {
Vue.prototype._init = function (options) {
// 做些什么
}
}
当函数initMixin被调用时,会向Vue构造函数的prototype属性添加 _init方法。执行new Vue()时,会调用 _init方法,该方法实现了一系列初始化操作,包括整个生命周期的流程以及响应式系统流程的启动等。
# 数据相关的实例方法
与数据相关的实例方法有:vm.$watch、vm.$set和vm.$delete
import {
set,
del
} from '../observer/index'
export function stateMixin (Vue) {
Vue.prototype.$set = set
Vue.prototype.$delete = del
Vue.prototype.$watch = function (expOrFn, cb, options) {}
}
# 事件相关的实例方法
与事件相关的实例方法有:vm.$on、vm.$once、vm.$off和vm.$emit
# vm.$on
vm.$on(event, callback)
参数:
{string | Array
用法:
监听当前实例上的自定义事件,事件可以由vm.$emit触发。回调函数会接收所有传入事件所触发的函数的额外参数。
示例:
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 {
//在执行new Vue()时,Vue会执行this._init方法进行一系列初始化操作,其中就会在Vue.js的实例上创建一个 _events属性,用来存储事件
(vm._events[event] || (vm._events[event] = [])).push(fn)
}
return vm
}
# vm.$off
vm.$off([event, callback])
参数:
- {string | Array
} event - {Function} callback
用法:移除自定义事件监听器。
- 如果没有提供参数,则移除所有的事件监听器。
- 如果只提供了事件,则移除该事件所有的监听器。
- 如果同时提供了事件与回调,则只移除这个回调的监听器。
Vue.prototype.$off = function (event, fn) {
const vm = this
// 处理没有提供参数的情况
if (!arguments.length) {
vm._events = Object.create(null)
return vm
}
// 处理event为数组的情况
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] = null
return vm
}
//处理提供了event和回调的情况 只移除与fn相同的监听器
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
vm.$once(event, callback)
参数:
- {string | Array
} event - {Function} callback
用法:监听一个自定义事件,但是只触发一次,在第一次触发之后移除监听器。
实现的思路:vm.$once中调用vm.$on来实现监听自定义事件的功能,当自定义事件触发后会执行拦截器,将监听器从事件列表中移除
Vue.prototype.$once = function (event, fn) {
const vm = this
function on () {
vm.$off(event, on)
fn.apply(vm, arguments)
}
on.fn = fn
vm.$on(event, on)
return vm
}
# vm.$emit
vm.$emit( event, [...args] )
参数:
- {string} event
- [...args]
用法:触发当前实例上的事件。附加参数都会传给监听器回调。
Vue.prototype.$emit = function (event) {
const vm = this
// 取出事件监听器回调函数列表
let cbs = vm._events[event]
if (cbs) {
// toArray的作用是将类似于数组的数据转换成真正的数组
// [].slice.call(arguments, 1)
const args = toArray(arguments, 1)
for (let i = 0, l = cbs.length; i < l; i++) {
try {
cbs[i].apply(vm, args)
} catch (e) {
handleError(e, vm, `event handler for "${event}"`)
}
}
}
return vm
}
# 生命周期相关的实例方法
相关的方法有vm.$mount、vm.$forceUpdate、vm.$nextTick和vm.$destroy。
export function lifecycleMixin (Vue) {
Vue.prototype.$forceUpdate = function () {
// 做点什么
}
Vue.prototype.$destroy = function () {
// 做点什么
}
}
export function renderMixin (Vue) {
Vue.prototype.$nextTick = function (fn) {
// 做点什么
}
}
vm.$mount方法则是在跨平台的代码中挂载到Vue构造函数的prototype属性上的
# vm.$forceUpdate
vm.$forceUpdate的原理:手动执行实例watcher的update方法,使Vue.js实例重新渲染
Vue.prototype.$forceUpdate = function () {
const vm = this
if (vm._watcher) {
vm._watcher.update()
}
}
vm.$forceUpdate仅仅影响实例本身以及插入插槽内容的子组件,而不是所有子组件。
# vm.$destroy
vm.$destroy的作用是完全销毁一个实例,它会清理该实例与其他实例的连接,并解绑其全部指令及监听器,同时会触发beforeDestroy和destroyed的钩子函数。
Vue.prototype.$destroy = function () {
const vm = this
// 防止vm.$destroy被反复执行
if (vm._isBeingDestroyed) {
return
}
// 销毁组件之前触发beforeDestroy钩子函数
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()
}
// Vue.js提供了vm.$watch方法,它允许用户监听某个状态。因此,还需要销毁用户使用vm.$watch所创建的watcher实例
let i = vm._watchers.length
while (i--) {
// 每当创建watcher实例时,都将watcher实例添加到vm._watchers中,当用户使用vm.$watch时,会在vm._watchers中添加一个watcher实例,通过vm._watchers就可以得到所有watcher实例,只需要遍历vm._watchers并依次执行每一项watcher实例的teardown方法,就可以将watcher实例从它所监听的状态的依赖列表中移除
vm._watchers[i].teardown()
}
// Vue.js实例添加 _isDestroyed属性来表示Vue.js实例已经被销毁
vm._isDestroyed = true
// 在vnode树上触发destroy钩子函数解绑指令
vm.__patch__(vm._vnode, null)
// 触发destroyed钩子函数
callHook(vm, 'destroyed')
// 移除所有的事件监听器
vm.$off()
}
export function remove (arr, item) {
if (arr.length) {
const index = arr.indexOf(item)
if (index > -1) {
return arr.splice(index, 1)
}
}
}
# vm.$nextTick
在Vue.js中,当状态发生变化时,watcher会得到通知,然后触发虚拟DOM的渲染流程。而watcher触发渲染这个操作并不是同步的,而是异步的。Vue.js中有一个队列,每当需要渲染时,会将watcher推送到这个队列中,在下一次事件循环中再让watcher触发渲染的流程。
当更新了状态(数据)后,需要对新DOM做一些操作,但是这时我们其实获取不到更新后的DOM,因为还没有重新渲染。这个时候我们需要使用nextTick方法
- 为什么vue.js使用异步更新策略
Vue.js 2.0开始使用虚拟DOM进行渲染,变化侦测的通知只发送到组件,组件内用到的所有状态的变化都会通知到同一个watcher,然后虚拟DOM会对整个组件进行“比对(diff)”并更改DOM。也就是说,如果在同一轮事件循环中有两个数据发生了变化,那么组件的watcher会收到两份通知,从而进行两次渲染。事实上,并不需要渲染两次,虚拟DOM会对整个组件进行渲染,所以只需要等所有状态都修改完毕后,一次性将整个组件的DOM渲染到最新即可。
要解决这个问题,Vue.js的实现方式是将收到通知的watcher实例添加到队列中缓存起来,并且在添加到队列之前检查其中是否已经存在相同的watcher,只有不存在时,才将watcher实例添加到队列中。然后在下一次事件循环(event loop)中,Vue.js会让队列中的watcher触发渲染流程并清空队列。这样就可以保证即便在同一事件循环中有两个状态发生改变,watcher最后也只执行一次渲染流程。
- 什么是事件循环
JS是一门单线程脚本语言,JS在执行过程中只有一个主线程处理任务,当执行到异步任务时会将这个任务放到事件队列中,被放到事件队列中的任务不会立即执行回到,而是等当前执行栈中所有同步任务执行完毕后,去检查微任务队列中是否有事件存在,如果存在就依次执行微任务队列中事件对应的回调,执行完后去宏任队列中取出一个任务加入到当前执行栈中,当执行栈的任务执行完后,再去微任务队列检查是否有事件,如此循环这个循环就叫事件循环。
异步任务分两种类型:
- 宏任务
- setTimeout
- setInterval
- setImmediate
- MessageChannel
- requestAnimationFrame
- I/O
- UI交互事件
- ...
- 微任务
- Promise.then
- MutationObserver
- Object.observe
- process.nextTick
- ...
- 什么是执行栈
当我们执行一个方法时,JavaScript会生成一个与这个方法对应的执行环境(context),又叫执行上下文。这个执行环境中有这个方法的私有作用域、上层作用域的指向、方法的参数、私有作用域中定义的变量以及this对象。这个执行环境会被添加到一个栈中,这个栈就是执行栈。
如果在这个方法的代码中执行到了一行函数调用语句,那么JavaScript会生成这个函数的执行环境并将其添加到执行栈中,然后进入这个执行环境继续执行其中的代码。执行完毕并返回结果后,JavaScript会退出执行环境并把这个执行环境从栈中销毁,回到上一个方法的执行环境。这个过程反复进行,直到执行栈中的代码全部执行完毕。这个执行环境的栈就是执行栈。
注意:修改数据一定要放在nextTick的前面,否则会得不到最新的DOM
new Vue({
// ……
methods: {
// ……
example: function () {
// 如果是先使用vm.$nextTick注册回调,然后修改数据,则在微任务队列中先执行使用vm.$nextTick注册的回调,然后执行更新DOM的回调。所以在回调中得不到最新的DOM,因为此时DOM还没有更新
// 先使用nextTick注册回调
this.$nextTick(function () {
// DOM没有更新
})
// 然后修改数据
this.message = 'changed'
}
}
})
Vue原型上的 $nextTick方法只是调用了nextTick方法
import { nextTick } from '../util/index'
// Vue原型上的 $nextTick方法调用了nextTick方法
Vue.prototype.$nextTick = function (fn) {
return nextTick(fn, this)
}
nextTick方法的实现
// 用来存储vm.$nextTick参数中提供的回调。
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 microTimerFunc
const p = Promise.resolve()
// 使用Promise.then将flushCallbacks添加到微任务队列中。
microTimerFunc = () => {
p.then(flushCallbacks)
}
export function nextTick (cb, ctx) {
callbacks.push(() => {
if (cb) {
cb.call(ctx)
}
})
if (!pending) {
pending = true
microTimerFunc()
}
}
// 测试一下
nextTick(function () {
console.log(this.name) // f
}, {name: 'f'})
# vm.$mount
vm.$mount( [elementOrSelector] )
参数:{Element | string} [elementOrSelector]
返回值:vm,即实例自身
用法: 如果Vue.js实例在实例化时没有收到el选项,则它处于“未挂载”状态,没有关联的DOM元素。我们可以使用vm.$mount手动挂载一个未挂载的实例。如果没有提供elementOrSelector参数,模板将被渲染为文档之外的元素,并且必须使用原生DOM的API把它插入文档中。这个方法返回实例自身,因而可以链式调用其他实例方法。
例子:
var MyComponent = Vue.extend({
template: '<div>Hello!</div>'
})
// 创建并挂载到#app(会替换#app)
new MyComponent().$mount('#app')
// 创建并挂载到#app(会替换#app)
new MyComponent({ el: '#app' })
// 或者,在文档之外渲染并且随后挂载
var component = new MyComponent().$mount()
document.getElementById('app').appendChild(component.$el)
在完整的构建版本中vm.$mount会检查模板是否已经转换成渲染函数。如果没有则进入编译过程,将模板编译成渲染函数,再进入挂载与渲染的流程。
运行时版本的vm.$mount默认实例上已存在渲染函数,如果不存在就设置一个。这个渲染函数在执行时会返回一个空节点的VNode,保证正常执行。
- 完整版vm.$mount的实现原理
// 函数劫持,可以在原始功能上扩展一些其他功能
const mount = Vue.prototype.$mount
Vue.prototype.$mount = function (el) {
// 获取el
el = el && query(el)
// 可以获取到一些用户设置的参数
const options = this.$options
// 是否存在渲染函数,不存在则将模板编译成渲染函数
if (!options.render) {
let template = options.template
//
if (template) {
if (typeof template === 'string') {
// 如果template是字符串且以#开头,则看作为选择器
if (template.charAt(0) === '#') {
// 获取DOM元素后将它的innerHTML作为模板
template = idToTemplate(template)
}
} else if (template.nodeType) {
// 如果是DOM元素则使用DOM元素的innerHTML作为模板
template = template.innerHTML
} else {
// 如果template不是字符串也不是DOM元素,会触发警告
if (process.env.NODE_ENV !== 'production') {
warn('invalid template option:' + template, this)
}
return this
}
} else if (el) {
// 如果用户没有提供template配置项,则从用户提供的el选项中获取模板
template = getOuterHTML(el)
}
// 编译相关逻辑
if (template) {
// 通过执行compileToFunctions函数将模板编译成渲染函数
const { render } = compileToFunctions(
template,
{...},
this
)
// 将渲染函数设置到this.$options上
options.render = render
}
}
return mount.call(this, el)
}
function query (el) {
if (typeof el === 'string') {
// 如果el是字符串(有可能是选择器)则通过querySelector获取元素
const selected = document.querySelector(el)
// 如果获取不到则创建一个空div标签
if (!selected) {
return document.createElement('div')
}
return selected
} else {
// 如果el不是字符串则认为是元素类型
return el
}
}
// 返回DOM元素的HTML字符串
function getOuterHTML (el) {
if (el.outerHTML) {
return el.outerHTML
} else {
const container = document.createElement('div')
container.appendChild(el.cloneNode(true))
return container.innerHTML
}
}
// 获取DOM元素后将它的innerHTML作为模板
function idToTemplate (id) {
const el = query(id)
return el && el.innerHTML
}
function compileToFunctions (template, options, vm) {
// 让options成为可选参数
options = extend({}, 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)
}
- 只包含运行时版本的vm.$mount的实现原理
Vue.prototype.$mount = function (el) {
el = el && inBrowser ? query(el) : undefined
// 将ID转换为DOM元素后,使用mountComponent函数将Vue.js实例挂载到DOM元素上
return mountComponent(this, el)
}
export function mountComponent (vm, el) {
// 是否存在渲染函数
if (!vm.$options.render) {
// 不存在,则设置一个默认的渲染函数createEmptyVNode,该渲染函数执行后,会返回一个注释类型的VNode节点,并在开发环境中给出警告
vm.$options.render = createEmptyVNode
if (process.env.NODE_ENV !== 'production') {
// 在开发环境发出警告
}
}
// 触发生命周期钩子
callHook(vm, 'beforeMount')
// 执行挂载操作,挂载是持续性的,挂载之后,每当状态发生变化时,都会进行渲染操作
vm._watcher = new Watcher(vm, () => {
// _update调用虚拟DOM中的patch方法来执行节点的比对与渲染操作
// _render的作用是执行渲染函数,得到一份最新的VNode节点树
// 这行代码的作用是先调用渲染函数得到一份最新的VNode节点树,然后通过 _update方法对最新的VNode和上一次渲染用到的旧VNode进行对比并更新DOM节点
vm._update(vm._render())
}, noop)
// 触发mounted钩子函数
callHook(vm, 'mounted')
return vm
}
watcher观察数据的过程:
状态通过Observer转换成响应式之后,每当触发getter时,会从全局的某个属性中获取watcher实例并将它添加到数据的依赖列表中。watcher在读取数据之前,会先将自己设置到全局的某个属性中。而数据被读取会触发getter,所以会将watcher收集到依赖列表中。收集好依赖后,当数据发生变化时,会向依赖列表中的watcher发送通知。
由于Watcher的第二个参数支持函数,所以当watcher执行函数时,函数中所读取的数据都将会触发getter去全局找到watcher并将其收集到函数的依赖列表中。也就是说,函数中读取的所有数据都将被watcher观察。这些数据中的任何一个发生变化时,watcher都将得到通知。
# 全局API的实现原理
Vue.extend( options )
参数:{Object} options
用法:使用基础Vue构造器创建一个“子类”,其参数是一个包含“组件选项”的对象。
例子:
<div id="mount-point"></div>
// 创建构造器
var Profile = Vue.extend({
template: '<p>{{firstName}} {{lastName}} aka {{alias}}</p>',
// data在Vue.extend()必须是函数
data: function () {
return {
firstName: 'Walter',
lastName: 'White',
alias: 'Heisenberg'
}
}
})
// 创建Profile实例,并挂载到一个元素上
new Profile().$mount('#mount-point')
代码:
let cid = 1
Vue.extend = function (extendOptions) {
extendOptions = extendOptions || {}
const Super = this
const SuperId = Super.cid
const cachedCtors = extendOptions._Ctor || (extendOptions._Ctor = {})
// 缓存,如果已经存在,直接返回缓存中的结果
if (cachedCtors[SuperId]) {
return cachedCtors[SuperId]
}
const name = extendOptions.name || Super.options.name
// 如果发现name选项不合规发出警告
if (process.env.NODE_ENV !== 'production') {
if (!/^[a-zA-Z][\w-]*$/.test(name)) {
warn(
'Invalid component name: "' + name + '". Component names ' +
'can only contain alphanumeric characters and the hyphen, ' +
'and must start with a letter.'
)
}
}
const Sub = function VueComponent (options) {
this._init(options)
}
// 父类的原型继承到子类中,继承Vue的能力
Sub.prototype = Object.create(Super.prototype)
Sub.prototype.constructor = Sub
// 为子类添加cid,它表示每个类的唯一标识
Sub.cid = cid++
// 合并了父类选项与子类选项
// mergeOptions方法会将两个选项合并为一个新对象
Sub.options = mergeOptions(
Super.options,
extendOptions
)
// 将父类保存到子类的super属性中
Sub['super'] = Super
// 如果选项中存在props属性,则初始化它,初始化props的作用是将key代理到 _props中。例如,vm.name实际上可以访问到的是Sub.prototype._props.name
if (Sub.options.props) {
initProps(Sub)
}
// 初始化computed
if (Sub.options.computed) {
initComputed(Sub)
}
// 将父类中存在的属性依次复制到子类中
Sub.extend = Super.extend
Sub.mixin = Super.mixin
Sub.use = Super.use
// ASSET_TYPES = ['component', 'directive', 'filter']
ASSET_TYPES.forEach(function (type) {
Sub[type] = Super[type]
})
if (name) {
Sub.options.components[name] = Sub
}
Sub.superOptions = Super.options
Sub.extendOptions = extendOptions
Sub.sealedOptions = extend({}, Sub.options)
// 使用父类的id作为缓存的key,将子类缓存在cachedCtors中
cachedCtors[SuperId] = Sub
return Sub
}
function initProps (Comp) {
const props = Comp.options.props
for (const key in props) {
proxy(Comp.prototype, `_props`, key)
}
}
function 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)
}
// 将computed对象遍历一遍,并将里面的每一项都定义一遍
function initComputed (Comp) {
const computed = Comp.options.computed
for (const key in computed) {
// computed对象中的每一项进行定义
defineComputed(Comp.prototype, key, computed[key])
}
}
Vue.nextTick( [callback, context] )
参数:
- {Function} [callback]
- {Object} [context]
用法:在下次DOM更新循环结束之后执行延迟回调,修改数据之后立即使用这个方法获取更新后的DOM
示例:
// 修改数据
vm.msg = 'Hello'
// DOM还没有更新
Vue.nextTick(function () {
// DOM更新了
})
// 作为一个Promise使用(这是Vue.js 2.1.0版本新增的)
Vue.nextTick()
.then(function () {
// DOM更新了
})
实现原理和前面的vm.$nextTick一样
import { nextTick } from '../util/index'
Vue.nextTick = nextTick
Vue.set( target, key, value )
参数:
- {Object | Array} target
- {string | number} key
- {any} value
返回值:设置的值
用法:设置对象的属性。如果对象是响应式的,确保属性被创建后也是响应式的,同时触发视图更新。这个方法主要用于避开Vue不能检测属性被添加的限制。
Vue.set与前面介绍的vm.$set的实现原理相同
import { set } from '../observer/index'
Vue.set = set
Vue.delete( target, key )
参数:
- {Object | Array} target
- {string | number} key/index
用法:删除对象的属性。如果对象是响应式的,确保删除能触发更新视图。这个方法主要用于避开Vue.js不能检测到属性被删除的限制。
Vue.delete与vm.$delete的实现原理相同
import { del } from '../observer/index'
Vue.delete = del
# Vue.directive
除了内置指令外,Vue还允许注册自定义指令
Vue.directive( id, [definition] )
参数:
- {string} id
- {Function | Object} [definition]
用法:注册或获取全局指令。
示例:
// 注册
Vue.directive('my-directive', {
bind: function () {},
inserted: function () {},
update: function () {},
componentUpdated: function () {},
unbind: function () {}
})
// 注册(指令函数)
Vue.directive('my-directive', function () {
// 这里将会被bind和update调用
})
// getter方法,返回已注册的指令
var myDirective = Vue.directive('my-directive')
代码:
// 用于保存指令的位置
Vue.options = Object.create(null)
Vue.options['directives'] = Object.create(null)
Vue.directive = function (id, definition) {
// 如果definition参数不存在,则使用id从this.options ['directives'] 中读出指令并将它返回
if (!definition) {
return this.options['directives'][id]
} else {
// 如果definition参数存在,则说明是注册操作,那么进而判断definition参数的类型是否是函数
if (typeof definition === 'function') {
// 如果是函数监听bind和update两个事件
definition = { bind: definition, update: definition }
}
// 如果definition不是函数,则说明它是用户自定义的指令对象,此时不需要做任何操作,直接将用户提供的指令对象保存在this.options['directives'] 上
this.options['directives'][id] = definition
return definition
}
}
# Vue.filter
Vue.filter( id, [definition] )
参数:
- {string} id
- {Function | Object} [definition]
用法:注册或获取全局过滤器。
// 注册
Vue.filter('my-filter', function (value) {
// 返回处理后的值
})
// getter方法,返回已注册的过滤器
var myFilter = Vue.filter('my-filter')
过滤器可以用在两个地方:双花括号插值和v-bind表达式,由“管道”符号指示。
<!-- 在双花括号中 -->
{{ message | capitalize }}
<!-- 在v-bind中 -->
<div v-bind:id="rawId | formatId"></div>
代码
Vue.options['filters'] = Object.create(null)
Vue.filter = function (id, definition) {
if (!definition) {
return this.options['filters'][id]
} else {
this.options['filters'][id] = definition
return definition
}
}
# Vue.component
Vue.component( id, [definition] )
参数:
- {string} id
- {Function | Object} [definition] 用法:注册或获取全局组件。注册组件时,还会自动使用给定的id设置组件的名称。
// 注册组件,传入一个扩展过的构造器
Vue.component('my-component', Vue.extend({ /* ... */ }))
// 注册组件,传入一个选项对象(自动调用Vue.extend)
Vue.component('my-component', { /* ... */ })
// 获取注册的组件(始终返回构造器)
var MyComponent = Vue.component('my-component')
代码:
// 新增components属性用于存放组件
Vue.options['components'] = Object.create(null)
//
Vue.component = function (id, definition) {
if (!definition) {
// 如果definition不存在,则使用id从this.options['components'] 中读出组件并将它返回
return this.options['components'][id]
} else {
// definition参数支持两种参数,分别是选项对象和构造器。组件其实是一个构造函数,是使用Vue.extend生成的子类,需要将参数definition统一处理成构造器
if (isPlainObject(definition)) {
// 如果选项对象中没有设置组件名,则自动使用给定的id设置组件的名称
definition.name = definition.name || id
// 调用Vue.extend方法将它变成Vue的子类
definition = Vue.extend(definition)
}
this.options['components'][id] = definition
return definition
}
}
Vue源码中Vue.directive、Vue.filter和Vue.component是放在一起实现的
Vue.options = Object.create(null)
// ASSET_TYPES = ['component', 'directive', 'filter']
ASSET_TYPES.forEach(type => {
Vue.options[type + 's'] = Object.create(null)
})
ASSET_TYPES.forEach(type => {
Vue[type] = function (id, definition) {
if (!definition) {
return this.options[type + 's'][id]
} else {
if (type === 'component' && isPlainObject(definition)) {
definition.name = definition.name || id
definition = Vue.extend(definition)
}
if (type === 'directive' && typeof definition === 'function') {
definition = { bind: definition, update: definition }
}
this.options[type + 's'][id] = definition
return definition
}
}
})
# Vue.use
Vue.use( plugin )
参数:{Object | Function} plugin
用法:安装Vue.js插件。如果插件是一个对象,必须提供install方法。如果插件是一个函数,它会被作为install方法。调用install方法时,会将Vue作为参数传入。install方法被同一个插件多次调用时,插件也只会被安装一次。
Vue.use = function (plugin) {
const installedPlugins = (this._installedPlugins || (this._installedPlugins = []))
// 判断插件是不是已经被注册过
if (installedPlugins.indexOf(plugin) > -1) {
return this
}
// 其他参数
const args = toArray(arguments, 1)
// 将Vue添加到args列表的最前面,保证install方法被执行时第一个参数是Vue,其余参数是注册插件时传入的参数。
args.unshift(this)
if (typeof plugin.install === 'function') {
plugin.install.apply(plugin, args)
} else if (typeof plugin === 'function') {
plugin.apply(null, args)
}
// 插件添加到installedPlugins中,保证相同的插件不会反复被注册
installedPlugins.push(plugin)
return this
}
# Vue.mixin
Vue.mixin( mixin )
参数:{Object} mixin
用法:全局注册一个混入(mixin),影响注册之后创建的每个Vue.js实例。插件作者可以使用混入向组件注入自定义行为(例如:监听生命周期钩子)。不推荐在应用代码中使用。
// 为自定义的选项myOption注入一个处理器
Vue.mixin({
created: function () {
var myOption = this.$options.myOption
if (myOption) {
console.log(myOption)
}
}
})
new Vue({
myOption: 'hello!'
})
// => "hello!"
实现原理
import { mergeOptions } from '../util/index'
export function initMixin (Vue) {
Vue.mixin = function (mixin) {
// mergeOptions方法会将用户传入的mixin与this.options合并成一个新对象,然后将这个生成的新对象覆盖this.options属性,这里的this.options其实就是Vue.options
this.options = mergeOptions(this.options, mixin)
return this
}
}
mixin方法修改了Vue.options属性,之后创建的每个实例都会用到该属性,会影响创建的每个实例。但也正是因为有影响,所以mixin在某些场景下才堪称神器。
# Vue.compile
Vue.compile( template )
参数:{string} template
用法:编译模板字符串并返回包含渲染函数的对象,只在完整版中才有效,因为只有完整版包含编译器
示例:
var res = Vue.compile('<div><span>{{ msg }}</span></div>')
new Vue({
data: {
msg: 'hello'
},
render: res.render
})
代码:
// Vue.compile方法只需要调用编译器就可以实现功能
Vue.compile = compileToFunctions
# Vue.version
作用:提供字符串形式的Vue.js安装版本号。这对社区的插件和组件来说非常有用,你可以根据不同的版本号采取不同的策略。
var version = Number(Vue.version.split('.')[0])
if (version === 2) {
// Vue.js v2.x.x
} else if (version === 1) {
// Vue.js v1.x.x
} else {
// 不支持的Vue.js版本
}
Vue.js在构建文件的配置中定义了 VERSION 常量,使用rollup-plugin-replace插件在构建的过程中将代码中的常量 VERSION 替换成package.json文件中的版本号