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

 更新时间:2022年11月1日 16:46  点击:668 作者: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内容请搜索猪先飞以前的文章或继续浏览下面的相关文章希望大家以后多多支持猪先飞!

原文出处:https://juejin.cn/post/7160640964829118495

[!--infotagslink--]

相关文章

  • 源码分析系列之json_encode()如何转化一个对象

    这篇文章主要介绍了源码分析系列之json_encode()如何转化一个对象,对json_encode()感兴趣的同学,可以参考下...2021-04-22
  • jQuery 2.0.3 源码分析之core(一)整体架构

    拜读一个开源框架,最想学到的就是设计的思想和实现的技巧。废话不多说,jquery这么多年了分析都写烂了,老早以前就拜读过,不过这几年都是做移动端,一直御用zepto, 最近抽出点时间把jquery又给扫一遍我也不会照本宣科的翻译...2014-05-31
  • vue3源码剖析之简单实现方法

    源码的重要性相信不用再多说什么了吧,特别是用Vue 框架的,一般在面试的时候面试官多多少少都会考察源码层面的内容,下面这篇文章主要给大家介绍了关于vue3源码剖析之简单实现的相关资料,需要的朋友可以参考下...2021-09-07
  • Underscore源码分析

    Underscore 是一个 JavaScript 工具库,它提供了一整套函数式编程的实用功能,但是没有扩展任何 JavaScript 内置对象。这篇文章主要介绍了underscore源码分析相关知识,感兴趣的朋友一起学习吧...2016-01-02
  • Android Studio如何查看源码并调试的方法步骤

    这篇文章主要介绍了Android Studio如何查看源码并调试的方法步骤,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧...2020-05-15
  • 源码揭秘为什么 Vue2 this 能够直接获取到 data 和 methods

    本篇文章主要介绍的是Vue2 this 能够直接获取到 data 和 methods,阅读本文将能学到如何学习调试 vue2 源码、data 中的数据为什么可以用 this 直接获取到、methods 中的方法为什么可以用 this 直接获取到,需要的朋友可以参考一下...2021-09-23
  • ASP.NET使用HttpWebRequest读取远程网页源代码

    本文分享了一个使用HttpWebRequest读取远程网页的案例,供大家参考学习。...2021-09-22
  • 用C#获取硬盘序列号,CPU序列号,网卡MAC地址的源码

    privatestring[]GetMoc() { string[]str=newstring[3]; ManagementClassmcCpu=newManagementClass("win32_Processor"); ManagementObjectCollectionmocCpu=mcCpu.GetInstan...2020-06-25
  • Php经典分页源码

    #********************************************************* #文件名称: function.php #功能描述: 新闻添加修改处理模块 #程序制作:留印(adleyliu) #联系qq :143...2016-11-25
  • .NET Core 源码编译的问题解析

    这篇文章主要介绍了.NET Core 源码编译的问题解析,本文通过实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下...2021-04-06
  • 一文帮你理解PReact10.5.13源码

    这篇文章主要介绍了一文帮你理解PReact10.5.13源码,代码简单易懂,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下...2021-04-04
  • C# 骑士飞行棋的源码(分享)

    以下是骑士飞行棋的源码,需要的朋友可以拿去用...2020-06-25
  • C语言 MD5的源码实例详解

    这篇文章主要介绍了C语言 MD5的源码实例详解的相关资料,需要的朋友可以参考下...2020-04-25
  • 使用C#获取网页HTML源码的例子

    这篇文章主要介绍了使用C#获取网页HTML源码的例子,考虑了区分GB2312和UTF-8编码,需要的朋友可以参考下...2020-06-25
  • IDEA2020.1构建Spring5.2.x源码的方法

    这篇文章主要介绍了IDEA2020.1构建Spring5.2.x源码的方法,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧...2020-10-09
  • C#中使用快速排序按文件创建时间将文件排序的源码

    快速排序类 using System; using System.Data; using System.Configuration; using System.Web; using System.Web.Security; using System.Web.UI; using System.We...2020-06-25
  • vue2中filter()的实现代码

    vue2.0里,不再有自带的过滤器,需要自己定义过滤器。下面通过实例代码给大家介绍vue2中filter()的相关知识,感兴趣的朋友一起看看吧...2020-04-20
  • Python优秀开源项目Rich源码解析的流程分析

    这篇文章主要介绍了Python优秀开源项目Rich源码解析,本文通过实例图文相结合给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下...2020-07-06
  • Vue2 的12种组件通讯

    之前文章我们描述给过Vue3 的七通信使用,今天中五篇文章我们再来看看Vue2 的通信使用写法的相关资料,需要的朋友可以参考下面文章的具体内容...2021-09-25
  • nGrinder性能工具源码安装部署过程

    nGrinder是NHN公司用Java语言开发的一款的基于Grinder开发的开源B/S Web性能测试平台,具有友好简洁的用户界面和分布式测试功能,本文给大家分享nGrinder性能工具源码安装部署过程,一起看看吧...2021-06-01