目录
- 前言
- Vue.extend()
- 基本使用
- 整体源码
- Vue.nextTick,Vue.set,Vue.delete
- Vue.directive、Vue.filter、Vue.component
- 基本使用
- 源码分析
- Vue.use
- 基本使用
- 源码预览
- Vue.mixin
- 基本使用
- 源码预览
- Vue.compile
- 基本使用
- 源码预览
- Vue.observable
- 基本用法
- 源码预览
- 总结
前言
实例方法是挂载在vue的原型上,而全局方法是挂载在vue上;全局方法有12个,分别为extend、nextTick、set、delete、directive、filter、component、use、mixin、version和observable;
Vue.extend()
基本使用
/*
作用:
使用基础vue构造器,创建一个子类,参数是一个包含组件选项的对象
`data` 选项是特例,需要注意 - 在 `Vue.extend()` 中它必须是函数
*/
Vue.extend(options)
// eg:
<div id="mount-point"></div>
// 创建构造器
var Profile = Vue.extend({
template: '<p>{{firstName}} {{lastName}} aka {{alias}}</p>',
data: function () {
return {
firstName: 'Walter',
lastName: 'White',
alias: 'Heisenberg'
}
}
})
// 创建 Profile 实例,并挂载到一个元素上。
new Profile().$mount('#mount-point')
// 结果:
<p>Walter White aka Heisenberg</p>
整体源码
// 源码位置 src/core/global-api/extend.js
Vue.extend = function (extendOptions: Object): Function {
// 初始化参数
extendOptions = extendOptions || {}
// 存储父类
const Super = this
// 存储父类的唯一标识
const SuperId = Super.cid
// 获取到参数中的缓存数据
const cachedCtors = extendOptions._Ctor || (extendOptions._Ctor = {})
// 如果已经缓存过就会缓存中获取返回
if (cachedCtors[SuperId]) {
return cachedCtors[SuperId]
}
// 获取到name
const name = extendOptions.name || Super.options.name
if (process.env.NODE_ENV !== 'production' && name) {
validateComponentName(name)
}
// 创建一个子类
const Sub = function VueComponent (options) {
this._init(options)
}
// 原型继承
Sub.prototype = Object.create(Super.prototype)
// 修改构造器
Sub.prototype.constructor = Sub
// 子类的唯一标识
Sub.cid = cid++
// 合并父类和传递进来的参数
Sub.options = mergeOptions(
Super.options,
extendOptions
)
// 子类中存储父类
Sub['super'] = Super
// 如果子类中有props 进行初始化
if (Sub.options.props) {
initProps(Sub)
}
// 如果子类中有计算属性,进行初始化
if (Sub.options.computed) {
initComputed(Sub)
}
// 把父类的extend,mixin,use放在子类上
Sub.extend = Super.extend
Sub.mixin = Super.mixin
Sub.use = Super.use
// 把指定的属性全部从父类上拷贝到子类上
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)
// cache constructor
// 缓存子类
cachedCtors[SuperId] = Sub
return Sub
}
}
// 把子类中的props存储到原型上
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])
}
}
extend方法内部其实就是创建一个子类,通过原型继承的方式继承父类,然后把父类的属性和一些方法存储到子类上,并且把子类进行缓存,再一开始就先从缓存中获取子类,如果没有就进行属性和方法的继承,最后返回子类;
Vue.nextTick,Vue.set,Vue.delete
以上三种都在实例方法中解析过
Vue.directive、Vue.filter、Vue.component
基本使用
注册或获取全局指令
// 注册
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.filter('my-filter',function(){})
// 获取
Vue.filter('my-filter')
注册或获取组件
// 注册 传入一个扩展过的构造器
Vue.component('my-component', Vue.extend({}))
// 注册 传入一个选项对象
Vue.component('my-component', {})
// 获取
Vue.component('my-component')
源码分析
// 源码位置 src/core/global-api/
export const ASSET_TYPES = [
'component',
'directive',
'filter'
]
export function initAssetRegisters (Vue: GlobalAPI) {
/**
* Create asset registration methods.
*/
ASSET_TYPES.forEach(type => {
Vue[type] = function (
id: string,
definition: Function | Object
): Function | Object | void {
// 不存在definition 表示获取,直接从options下的相应的typs+'s'进行获取
if (!definition) {
return this.options[type + 's'][id]
} else {
/* istanbul ignore if */
if (process.env.NODE_ENV !== 'production' && type === 'component') {
validateComponentName(id)
}
// 如果是组件 并且definition为一个{}对象,那么就通过extend继承vue
if (type === 'component' && isPlainObject(definition)) {
// 没有name就使用id
definition.name = definition.name || id
// 使definition继承vue
definition = this.options._base.extend(definition)
}
// 如果是指令并且definition是一个函数,那么就把这个函数作为bind和update
if (type === 'directive' && typeof definition === 'function') {
definition = { bind: definition, update: definition }
}
// 设置值 保存到对应的key下
this.options[type + 's'][id] = definition
return definition
}
}
})
}
指令,过滤器,组件把它们放在一个数组中,遍历这个数组,分别进行处理
export const ASSET_TYPES = [
'component',
'directive',
'filter'
]
ASSET_TYPES.forEach(type => {
Vue[type] = function (
id: string,
definition: Function | Object
){}
})
如果没有传递第二个参数,表示是获取,直接进行获取返回
if (!definition) {
return this.options[type + 's'][id]
}
否则就是传递了第二个参数,表示是注册,首先判断是否是组件,是组件并且第二个参数是对象,那么就继承Vue;
// 如果是组件
if (type === 'component' && isPlainObject(definition)) {
// 没有name就使用id
definition.name = definition.name || id
// 使definition继承vue
definition = this.options._base.extend(definition)
}
如果是指令,并且第二个参数是函数,那么直接放在一个对象中
// 如果是指令
if (type === 'directive' && typeof definition === 'function') {
definition = { bind: definition, update: definition }
}
最后给相应的对象下的自定义指令或过滤器或组件进行赋值并返回
// 设置值
this.options[type + 's'][id] = definition
return definition
通过以上三种方法就可以自定义组件,过滤器和指令,然后就可以在模板中使用它们,最终的处理还是在模板编译节点进行的;
Vue.use
基本使用
/*
作用:
安装 Vue.js 插件。如果插件是一个对象,必须提供 `install` 方法。
如果插件是一个函数,它会被作为 install 方法。install 方法调用时,会将 Vue 作为参数传入。
该方法需要在调用 `new Vue()` 之前被调用。
当 install 方法被同一个插件多次调用,插件将只会被安装一次
plugin:- `{Object | Function} plugin`
*/
Vue.use( plugin )
源码预览
// 源码位置 src/core/global-api/use.js
export function initUse (Vue: GlobalAPI) {
Vue.use = function (plugin: Function | Object) {
// 初始_installedPlugins变量
const installedPlugins = (this._installedPlugins || (this._installedPlugins = []))
// 如果已经存在直接返回
if (installedPlugins.indexOf(plugin) > -) {
return this
}
// 获取剩余的参数转成数组
const args = toArray(arguments,)
// 把this添加进去
args.unshift(this)
// 如果install是一个函数 直接执行
if (typeof plugin.install === 'function') {
plugin.install.apply(plugin, args)
} else if (typeof plugin === 'function') { // 如果插件是一个函数,直接执行
plugin.apply(null, args)
}
// 添加到数组中
installedPlugins.push(plugin)
return this
}
}
首先获取到用来缓存的属性,如果不存在初始化为一个数组,再判断传递进来的插件是否已经存在,存在表示重复操作,直接返回;(实现:当 install 方法被同一个插件多次调用,插件将只会被安装一次)
// 初始_installedPlugins变量
const installedPlugins = (this._installedPlugins || (this._installedPlugins = []))
// 如果已经存在直接返回
if (installedPlugins.indexOf(plugin) > -) {
return this
}
接着获取到剩余的参数,并且把当前实例也作为参数(实现:install 方法调用时,会将 Vue 作为参数传入。)
// 获取剩余的参数转成数组
const args = toArray(arguments,)
// 把this添加进去
args.unshift(this)
判断plugin.install是否是一个函数,如果是函数就通过plugin执行(实现:如果插件是一个对象,必须提供 install 方法)
// 如果install是一个函数 直接执行
if (typeof plugin.install === 'function') {
plugin.install.apply(plugin, args)
}
否则plugin是一个函数,那么直接执行(实现:如果插件是一个函数,它会被作为 install 方法)
else if (typeof plugin === 'function') { // 如果插件是一个函数,直接执行
plugin.apply(null, args)
}
最后添加到缓存中并返回
// 添加到数组中
installedPlugins.push(plugin)
return this
Vue.mixin
基本使用
/*
作用:
全局注册一个混入,影响注册之后所有创建的每个vue实例,插件作者可以使用混入,
向组件注入自定义的行为。**不推荐在应用代码中使用**。
*/
Vue.mixin()
源码预览
// 源码位置 src/core/global-api/mixin.js
export function initMixin (Vue: GlobalAPI) {
Vue.mixin = function (mixin: Object) {
this.options = mergeOptions(this.options, mixin)
return this
}
}
混入很简单,就是把传递进来的数据和实例上已有的数据进行合并替换,最后重新赋值给实例上的options;
export function mergeOptions (
parent: Object,
child: Object,
vm?: Component
): Object {
if (process.env.NODE_ENV !== 'production') {
checkComponents(child)
}
if (typeof child === 'function') {
child = child.options
}
// 设置props,把带有-的属性名改成驼峰,存到一个新的对象中进行返回
normalizeProps(child, vm)
// 设置注入的属性 把inject中的数据都转成 {from:val}形式的对象返回
normalizeInject(child, vm)
// 设置指令 返回指令的值为{ bind: def, update: def }的形式
normalizeDirectives(child)
// Apply extends and mixins on the child options,
// but only if it is a raw options object that isn't
// the result of another mergeOptions call.
// Only merged options has the _base property.
// 递归处理extends和mixins
if (!child._base) {
if (child.extends) {
parent = mergeOptions(parent, child.extends, vm)
}
if (child.mixins) {
for (let i =, l = child.mixins.length; i < l; i++) {
parent = mergeOptions(parent, child.mixins[i], vm)
}
}
}
// 创建一个新的对象
const options = {}
let key
for (key in parent) { // 遍历父级进行设置属性
mergeField(key)
}
for (key in child) { // 遍历子级进行设置属性
if (!hasOwn(parent, key)) { // 父级不存在此属性就进行设置
mergeField(key)
}
}
// 通过策略模式给不同的属性设置对应的执行函数,
function mergeField (key) {
// 获取到不同属性的函数
const strat = strats[key] || defaultStrat
// 执行对应的函数重新设置属性
options[key] = strat(parent[key], child[key], vm, key)
}
return options
}
把子级的属性进行单独处理,处理完成之后和父级的属性进行合并,合并之后进行返回;
Vue.compile
基本使用
/*
作用: 将一个模板字符串编译成 render 函数。**只在完整版时可用**。
template: 字符串模板
*/
Vue.compile(template)
源码预览
Vue.compile = compileToFunctions
compileToFunctions函数在模板编译节点已经解析完
Vue.observable
基本用法
/*
作用:
让一个对象可响应,Vue内部会用它来处理data函数返回的对象
返回的对象可以直接用于[渲染函数]和[计算属性]内,并且会在发生变更时触发相应的更新。
也可以作为最小化的跨组件状态存储器,用于简单的场景:
*/
const state = Vue.observable({ count: })
const Demo = {
render(h) {
return h('button', {
on: { click: () => { state.count++ }}
}, `count is: ${state.count}`)
}
}
源码预览
// 源码位置 src/core/global-api/
//.6 explicit observable API
Vue.observable = <T>(obj: T): T => {
observe(obj)
return obj
}
总结
extend: 内部实现一个子类,通过原型继承的方式继承自当前调用者(父类),并且把父类中的属性进行合并,存储到自己的options中,父类的方法添加到自己构造器上;把子类进行缓存;一进入到函数内部首先从缓存中获取,没有才进行创建子类;
directive,filter,component: 这三种方式通过一个函数实现,首先判断有没有传递第二个参数,如果没传递表示是获取,直接获取返回;如果有传递表示注册,如果是指令并且第二个参数是函数,那么把函数作为bind和update的值;如果是组件并且第二个参数是函数,那么通过extend方法进行继承;
use: 如果缓存中有此插件就直接返回,否则就判断传递进来的参数是对象还是函数,如果是对象那么就执行对象上的install方法,如果是函数直接执行;并且把当前实例作为参数传递进去;最后把插件进行缓存;
mixin: 首先把props,inject和指令的属性进行统一格式化操作;然后把当前实例的options和传递进来的参数进行合并,相同属性传递进来的参数会覆盖options中的属性;
observable: 通过调用observe给当前传递进来的对象添加响应式,并且返回该对象;