vue2源码解析之全局API实例详解
作者:1undefined2
前言
实例方法是挂载在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) > -1) { return this } // 获取剩余的参数转成数组 const args = toArray(arguments, 1) // 把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) > -1) { return this }
接着获取到剩余的参数,并且把当前实例也作为参数(实现:install 方法调用时,会将 Vue 作为参数传入。)
// 获取剩余的参数转成数组 const args = toArray(arguments, 1) // 把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 = 0, 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: 0 }) const Demo = { render(h) { return h('button', { on: { click: () => { state.count++ }} }, `count is: ${state.count}`) } }
源码预览
// 源码位置 src/core/global-api/ // 2.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给当前传递进来的对象添加响应式,并且返回该对象;
到此这篇关于vue2源码解析之全局API实例详解的文章就介绍到这了,更多相关vue2全局API内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!