我的编程空间,编程开发者的网络收藏夹
学习永远不晚

vue2源码解析之全局API实例详解

短信预约 -IT技能 免费直播动态提醒
省份

北京

  • 北京
  • 上海
  • 天津
  • 重庆
  • 河北
  • 山东
  • 辽宁
  • 黑龙江
  • 吉林
  • 甘肃
  • 青海
  • 河南
  • 江苏
  • 湖北
  • 湖南
  • 江西
  • 浙江
  • 广东
  • 云南
  • 福建
  • 海南
  • 山西
  • 四川
  • 陕西
  • 贵州
  • 安徽
  • 广西
  • 内蒙
  • 西藏
  • 新疆
  • 宁夏
  • 兵团
手机号立即预约

请填写图片验证码后获取短信验证码

看不清楚,换张图片

免费获取短信验证码

vue2源码解析之全局API实例详解

前言

实例方法是挂载在vue的原型上,而全局方法是挂载在vue上;全局方法有12个,分别为extend、nextTick、set、delete、directive、filter、component、use、mixin、version和observable;

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>

整体源码

// 源码位置 class="lazy" data-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')

源码分析

// 源码位置 class="lazy" data-src/core/global-api/
export const ASSET_TYPES = [
  'component',
  'directive',
  'filter'
]

export function initAssetRegisters (Vue: GlobalAPI) {
  
  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 {
        
        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.use( plugin )

源码预览

// 源码位置 class="lazy" data-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.mixin()

源码预览

// 源码位置 class="lazy" data-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

基本使用


Vue.compile(template)

源码预览

Vue.compile = compileToFunctions

compileToFunctions函数在模板编译节点已经解析完

Vue.observable

基本用法


const state = Vue.observable({ count: 0 })  
const Demo = {  
    render(h) {  
        return h('button', {  
            on: { click: () => { state.count++ }}  
        }, `count is: ${state.count}`)  
    }  
}

源码预览

// 源码位置 class="lazy" data-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内容请搜索编程网以前的文章或继续浏览下面的相关文章希望大家以后多多支持编程网!

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

vue2源码解析之全局API实例详解

下载Word文档到电脑,方便收藏和打印~

下载Word文档

猜你喜欢

vue2源码解析之全局API实例详解

全局API并不在构造器里,而是先声明全局变量或者直接在Vue上定义一些新功能,Vue内置了一些全局API,下面这篇文章主要给大家介绍了关于vue2源码解析之全局API的相关资料,需要的朋友可以参考下
2022-11-13

vue2.x中keep-alive源码解析(实例代码)

Keep-Alive模式避免频繁创建、销毁链接,允许多个请求和响应使用同一个HTTP链接,这篇文章主要介绍了vue2.x中keep-alive源码解析,需要的朋友可以参考下
2023-02-14

Go并发之RWMutex的源码解析详解

RWMutex是一个支持并行读串行写的读写锁。RWMutex具有写操作优先的特点,写操作发生时,仅允许正在执行的读操作执行,后续的读操作都会被阻塞。本文就来从源码解析一下RWMutex的使用
2023-03-15

Android源码解析之属性动画详解

前言 大家在日常开发中离不开动画,属性动画更为强大,我们不仅要知道如何使用,更要知道他的原理。这样,才能得心应手。那么,今天,就从最简单的来说,了解下属性动画的原理。ObjectAnimator.ofInt(mView,"width",10
2022-06-06

Java源码解析之ConcurrentHashMap的示例分析

小编给大家分享一下Java源码解析之ConcurrentHashMap的示例分析,希望大家阅读完这篇文章之后都有所收获,下面让我们一起去探讨吧!早期 ConcurrentHashMap,其实现是基于:分离锁,也就是将内部进行分段(Segme
2023-06-15

Android全局获取Context实例详解

Android全局获取Context实例详解在弹出Toast 启动活动 发送广播 操作数据库 使用通知等等时都需要Context如果操作在活动中进行是很简单的,因为活动本身就是一个Context对象但是当逻辑代码脱离了Activity类,此
2023-05-31

nodejs中全局变量的实例解析

1.global 类似于客户端javascript运行环境中的window module1.js:module.exports={}; //耻辱的使用了全局变量 global.varA = "abc";关于global对象,实际使用中可以省
2022-06-04

Android 全局异常捕获实例详解

Android 全局异常捕获今天就来说说作为程序猿的我们每天都会遇到的东西bug,出bug不可怕可怕的是没有出bug时的堆栈信息,那么对于bug的信息收集就显得尤为重要了,一般用第三方bugly或者友盟等等都能轻易收集,但是由于公司不让使用
2023-05-31

Android编程动态加载布局实例详解【附demo源码】

本文实例讲述了Android编程动态加载布局的方法。分享给大家供大家参考,具体如下: 由于前段时间项目需要,需要在一个页面上加载根据不同的按钮加载不同的布局页面,当时想到用 tabhot 。不过美工提供的界面图完全用不上tabhot ,所以
2022-06-06

编程热搜

目录