返回顶部
首页 > 资讯 > 前端开发 > JavaScript >vue2源码解析之全局API实例详解
  • 387
分享到

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

vue2全局apivue2.0源码解析vuejs源码解析 2022-11-13 19:11:13 387人浏览 独家记忆
摘要

目录前言Vue.extend()基本使用整体源码Vue.nextTick,Vue.set,Vue.deleteVue.directive、Vue.filter、Vue.co

前言

实例方法是挂载在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>

整体源码

// 源码位置 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) {
  
  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 )

源码预览

// 源码位置 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()

源码预览

// 源码位置 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}`)  
    }  
}

源码预览

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

--结束END--

本文标题: vue2源码解析之全局API实例详解

本文链接: https://lsjlt.com/news/170545.html(转载时请注明来源链接)

有问题或投稿请发送至: 邮箱/279061341@qq.com    QQ/279061341

猜你喜欢
  • vue2源码解析之全局API实例详解
    目录前言Vue.extend()基本使用整体源码Vue.nextTick,Vue.set,Vue.deleteVue.directive、Vue.filter、Vue.co...
    99+
    2022-11-13
    vue2全局api vue2.0源码解析 vuejs源码解析
  • vue2.x中keep-alive源码解析(实例代码)
    目录一、前世尘缘二、keep-alive内置组件1.缓存动态组件2.缓存路由组件3.原理解析三、LRU算法一、前世尘缘 vue中内置组件keep-alive的设计思想源于HTTP中的...
    99+
    2023-02-14
    vue2.x keep-alive源码 vue keep-alive源码 vue2.x源码解析
  • OpenJDK源码解析之System.out.println详解
    目录一、前戏二、JVM源码分析三、坑?四、总结一、前戏 可能不少小伙伴习惯在代码中使用sout打印一些信息,就像这样: System.out.println("hello wor...
    99+
    2024-04-02
  • Java源码解析之详解ImmutableMap
    一、案例场景 遇到过这样的场景,在定义一个static修饰的Map时,使用了大量的put()方法赋值,就类似这样—— public static final Map<St...
    99+
    2024-04-02
  • Java源码解析之详解ReentrantLock
    ReentrantLock ReentrantLock是一种可重入的互斥锁,它的行为和作用与关键字synchronized有些类似,在并发场景下可以让多个线程按照一定的顺序访问同一资...
    99+
    2024-04-02
  • python源码剖析之PyObject详解
    目录一、Python中的对象1.1 对象机制的基石PyObject二、类型对象2.1 对象的创建2.2 对象的行为2.3 类型的类型三、Python的多态性四、引用计数五、Pytho...
    99+
    2024-04-02
  • 解析Vue2实现composition API的原理
    自从 Vue3 发布之后,composition API 这个词走入写 Vue 同学的视野之中,相信大家也一直听到 composition API 比之前的 options API 有多好多强,如今由于 @vue/composition-a...
    99+
    2023-05-14
    Vue.js 前端
  • Go Excelize API源码阅读SetSheetViewOptions示例解析
    目录一、Go-Excelize简介二、 SetSheetViewOptions一、Go-Excelize简介 Excelize 是 Go 语言编写的用于操作 Office Excel...
    99+
    2024-04-02
  • Go Excelize API源码解析GetSheetFormatPr使用示例
    目录一、Go-Excelize简介二、GetSheetFormatPr一、Go-Excelize简介 Excelize 是 Go 语言编写的用于操作 Office Excel 文档基...
    99+
    2024-04-02
  • Android源码解析之属性动画详解
    前言 大家在日常开发中离不开动画,属性动画更为强大,我们不仅要知道如何使用,更要知道他的原理。这样,才能得心应手。那么,今天,就从最简单的来说,了解下属性动画的原理。 Obj...
    99+
    2022-06-06
    属性 动画 Android
  • Go并发之RWMutex的源码解析详解
    目录使用场景源码解析RWMutex结构体Lock()方法Unlock()方法RLock()方法RUnlock()方法RWMutex是一个支持并行读串行写的读写锁。RWMutex具有写...
    99+
    2023-03-15
    Go RWMutex源码 Go RWMutex
  • JVM完全解读之Metaspace解密源码分析
    概述 metaspace,顾名思义,元数据空间,专门用来存元数据的,它是jdk8里特有的数据结构用来替代perm,这块空间很有自己的特点,前段时间公司这块的问题太多了,主要是因为升级...
    99+
    2024-04-02
  • Java源码解析之ConcurrentHashMap的示例分析
    小编给大家分享一下Java源码解析之ConcurrentHashMap的示例分析,希望大家阅读完这篇文章之后都有所收获,下面让我们一起去探讨吧!早期 ConcurrentHashMap,其实现是基于:分离锁,也就是将内部进行分段(Segme...
    99+
    2023-06-15
  • Android全局获取Context实例详解
    Android全局获取Context实例详解在弹出Toast 启动活动 发送广播 操作数据库 使用通知等等时都需要Context如果操作在活动中进行是很简单的,因为活动本身就是一个Context对象但是当逻辑代码脱离了Activity类,此...
    99+
    2023-05-31
    android context roi
  • MyBatis3源码解析之如何获取数据源详解
    目录前言jdbc传统JDBC弊端思考源码分析获取数据源总结前言 上文讲的MyBatis部署运行且根据官网运行了一个demo:一步到位部署运行MyBatis3源码<保姆级>...
    99+
    2024-04-02
  • nodejs中全局变量的实例解析
    1.global 类似于客户端javascript运行环境中的window module1.js: module.exports={}; //耻辱的使用了全局变量 global.varA = "abc"...
    99+
    2022-06-04
    实例 全局变量 nodejs
  • Vue源码分析之虚拟DOM详解
    为什么需要虚拟dom? 虚拟DOM就是为了解决浏览器性能问题而被设计出来的。例如,若一次操作中有10次更新DOM的动作,虚拟DOM不会立即操作DOM,而是将这10次更新的diff内...
    99+
    2024-04-02
  • Android 全局异常捕获实例详解
    Android 全局异常捕获今天就来说说作为程序猿的我们每天都会遇到的东西bug,出bug不可怕可怕的是没有出bug时的堆栈信息,那么对于bug的信息收集就显得尤为重要了,一般用第三方bugly或者友盟等等都能轻易收集,但是由于公司不让使用...
    99+
    2023-05-31
    android 全局 异常捕获
  • java TreeMap源码解析详解
    java TreeMap源码解析详解 在介绍TreeMap之前,我们来了解一种数据结构:排序二叉树。相信学过数据结构的同学知道,这种结构的数据存储形式在查找的时候效率非常高。如图所示,这种数据结构是以二叉树为基础的,所有的左孩子的...
    99+
    2023-05-31
    java treemap 源码
  • 解析Linux源码之epoll
    目录一、前言二、简单的epoll例子2.1、epoll_create2.2、struct eventpoll2.3、epoll_ctl(add)2.4、ep_insert2.5、tfile->f_op->...
    99+
    2022-06-03
    Linux 源码 Linux epoll
软考高级职称资格查询
编程网,编程工程师的家园,是目前国内优秀的开源技术社区之一,形成了由开源软件库、代码分享、资讯、协作翻译、讨论区和博客等几大频道内容,为IT开发者提供了一个发现、使用、并交流开源技术的平台。
  • 官方手机版

  • 微信公众号

  • 商务合作