从 vue 源码看问题 —— 你真的了解 Vue 全局 Api 吗?(上)

简介: 从 vue 源码看问题 —— 你真的了解 Vue 全局 Api 吗?

image.png


前言

在使用 Vue 框架在进行开发时,不免会使用到一些 global-api 或者是实例上的 api(本质上也是调用了全局 api ,相当于是别名),用于解决在项目遇到的一些问题,比如:this.$set()、this.$nextTick() 等等。还有一些全局 api 很可能只是因为不了解这个 api 的作用,所以一直没有找到合适的场景去使用它,那现在我们就通过源码层面看看这些全局 api 的作用是什么,以及了解其对应的原理。

下面是【官方文档目录】和【源码目录】的对比:

image.png

深入源码

initGlobalAPI() 方法

文件位置:src\core\global-api\index.js

初始化全局 api,比如:Vue.util = {...}、Vue.options = {...}、Vue.[set | del | nextTick | observable | use | mixin | extend | component | directive | filter],详细内容请看下面代码的注释.

// 初始化全局 api 的入口
export function initGlobalAPI (Vue: GlobalAPI) {
  // Vue 全局默认配置 config
  const configDef = {}
  configDef.get = () => config
  // 不允许通过 Vue.config = {} 的方式进行覆盖
  if (process.env.NODE_ENV !== 'production') {
    configDef.set = () => {
      warn(
        'Do not replace the Vue.config object, set individual fields instead.'
      )
    }
  }
  // 将配置项 config 代理到 Vue 上,支持通过 Vue.config 的方式去访问
  Object.defineProperty(Vue, 'config', configDef)
  // exposed util methods.
  // NOTE: these are not considered part of the public API - avoid relying on
  // them unless you are aware of the risk.
  // 向外暴露了一些工具方法
  Vue.util = {
    // 警告日志
    warn,
    // extend (to: Object, _from: ?Object), 将 _from 对象上的属性复制到 to 对象
    extend,
    // 合并配置项
    mergeOptions,
    // 设置 getter 和 setter,分别进行 依赖收集 和 依赖更新通知
    defineReactive
  }
  // 全局 set 方法,处理数组元素或对象属性的新增或修改
  Vue.set = set
  // 全局 delete 方法,删除数组元素或对象属性
  Vue.delete = del
  // 全局 nextTick 方法,主要依赖于浏览的异步任务队列
  Vue.nextTick = nextTick
  // 2.6 explicit observable API
  // 全局 observable 方法,本质就是 observe 方法,将接收对象转换为响应式对象
  Vue.observable = <>(obj: T): T => {
    observe(obj)
    return obj
  }
 {/* 为全局 options 设置指定的配置项 Vue.options = { components:{}, directive: {}, filters:{} }  */}
  Vue.options = Object.create(null)
  ASSET_TYPES.forEach(type => {
    Vue.options[type + 's'] = Object.create(null)
  })
  // this is used to identify the "base" constructor to extend all plain-object
  // components with in Weex's multi-instance scenarios.
  {/* 将 Vue 赋值给 Vue.options._base,向外进行暴露  */}
  Vue.options._base = Vue
  {/* 
   builtInComponents 实际上就是 KeepAlive 组件
   将 KeepAlive 注册到 components 全局组件配置当中,即可以直接在全局使用 <keep-alive></keep-alive>
  */}
  extend(Vue.options.components, builtInComponents)
 {/* 初始化 Vue.use 方法 */}
  initUse(Vue)
  {/* 初始化 Vue.mixin 方法 */}
  initMixin(Vue)
  {/* 初始化 Vue.extend 方法 */}
  initExtend(Vue)
  {/* 初始化 Vue.component、Vue.directive、Vue.filter 方法 */}
  initAssetRegisters(Vue)
}
复制代码

Vue.set() 方法

文件位置:src\core\observer\index.js

实例上的 vm.$set 是全局 Vue.set别名,目的是向响应式对象中添加一个 property,并确保这个新 property 同样是响应式的,如果 key 已存在就将新值替换旧值,且触发视图更新.

export function set (target: Array<any> | Object, key: any, val: any): any {
  if (process.env.NODE_ENV !== 'production' &&
    (isUndef(target) || isPrimitive(target))
  ) {
    warn(`Cannot set reactive property on undefined, null, or primitive value: ${(target: any)}`)
  }
  // 处理数组:Vue.set(arr, index, value),实现响应式
  if (Array.isArray(target) && isValidArrayIndex(key)) {
    target.length = Math.max(target.length, key)
    // 本质上是通过重写后的 splice 方法进行实现元素替换
    target.splice(key, 1, val)
    return val
  }
  /*
     处理对象: Vue 无法探测普通的新增 property,如果需要可以通过 Vue.set(this, newKey, value) 进行设置动态的、具有响应式的属性
  */ 
 // 当前对象上存在 key 属性且不属于原型对象上时,直接更新旧值
  if (key in target && !(key in Object.prototype)) {
    target[key] = val
    return val
  }
  // 判断当前对象是否经过 observer 处理,__ob__ 存在即已处理,否则表明是普通对象
  const ob = (target: any).__ob__
  // 避免在运行时给 Vue 实例或者根组件的 $data 上直接添加属性或修改属性值
  if (target._isVue || (ob && ob.vmCount)) {
    process.env.NODE_ENV !== 'production' && warn(
      'Avoid adding reactive properties to a Vue instance or its root $data ' +
      'at runtime - declare it upfront in the data option.'
    )
    return val
  }
  // 给普通对象进行 Vue.set(obj, key, value) 是可以设置成功的,但是不具备响应式 
  if (!ob) {
    target[key] = val
    return val
  }
  // 对新属性设置 getter 和 setter,读取时进行依赖收集,设置时进行依赖更新通知
  defineReactive(ob.value, key, val)
  // 直接进行依赖更新通知
  ob.dep.notify()
  return val
}
复制代码

Vue.delete() 方法

文件位置:src\core\observer\index.js

实例上的 vm.$delete 是全局 Vue.delete 方法的 别名,目的是在删除对象的 property 时,若对象是响应式的,确保删除能触发更新视图.

export function del (target: Array<any> | Object, key: any) {
  if (process.env.NODE_ENV !== 'production' &&
    (isUndef(target) || isPrimitive(target))
  ) {
    warn(`Cannot delete reactive property on undefined, null, or primitive value: ${(target: any)}`)
  }
  // 删除数组元素:利用重写后的 splice 方法删除元素
  if (Array.isArray(target) && isValidArrayIndex(key)) {
    target.splice(key, 1)
    return
  }
  // 删除对象属性
  const ob = (target: any).__ob__
  if (target._isVue || (ob && ob.vmCount)) {
    process.env.NODE_ENV !== 'production' && warn(
      'Avoid deleting properties on a Vue instance or its root $data ' +
      '- just set it to null.'
    )
    return
  }
  // 当前 key 不存在当前对象上,直接终止
  if (!hasOwn(target, key)) {
    return
  }
  // 通过 delete 操作符删除对象属性
  delete target[key]
  // 如果是普通对象,删除属性后不进行依赖更新通知
  if (!ob) {
    return
  }
  // 如果是响应式对象,删除属性后进行依赖更新通知
  ob.dep.notify()
}
复制代码

Vue.nextTick() 方法

文件位置:src\core\util\next-tick.js

将回调延迟到下次 DOM 更新循环之后执行,在修改数据之后立即使用它,然后等待 DOM 更新。

与全局方法 Vue.nextTick 一样,不同的是回调的 this 自动绑定到调用它的实例上。

在前面的文章中有对 nextTick 的详细介绍,可以通过 nextTick 更详细介绍 进行查看.

export function nextTick(cb?: Function, ctx?: Object) {
  let _resolve
  // 将 cb 通过匿名函数包裹一层,然后存入到 callbacks 中
  callbacks.push(() => {
    // cb 可能是 Vue 内部传递的 flushSchedulerQueue 函数,也可能是用户在外部传入的自定义函数,因此这里需要对 cb 进行 try catch,方便捕获异常
    if (cb) {
      try {
        cb.call(ctx)
      } catch (e) {
        handleError(e, ctx, 'nextTick')
      }
    } else if (_resolve) {
      // cb 不存在,默认执行在下面 _resolve = resolve 方法
      _resolve(ctx)
    }
  })
  // pending = false 时,需要执行 timerFunc()
  if (!pending) {
    pending = true
    // 利用浏览器的异步任务执行 flushCallbacks() 函数
    timerFunc()
  }
  // 当 cb 函数不存在且支持使用 Promise 时,需要提供一个默认函数,即 Promise 中的 resolve 方法 
  if (!cb && typeof Promise !== 'undefined') {
    return new Promise(resolve => {
      _resolve = resolve
    })
  }
}
复制代码

Vue.observable() 方法

文件位置:src\core\observer\index.js

实际上是调用的 observe() 方法,目的是让一个对象具有响应式,Vue 内部通常会用它来处理 data 函数返回的对象.

返回的对象可以直接用于 渲染函数计算属性 内,并且会在发生变更时触发相应的更新.

在前面的文章中有对 observe 的详细介绍,可以通过 observer 更详细介绍 查看.

export function observe (value: any, asRootData: ?boolean): Observer | void {
  if (!isObject(value) || value instanceof VNode) {
    return
  }
  let ob: Observer | void
  // value 已经通过 Observer 处理,直接返回上一次的 __ob__ 实例
  if (hasOwn(value, '__ob__') && value.__ob__ instanceof Observer) {
    ob = value.__ob__
  } else if (
    shouldObserve &&
    !isServerRendering() &&
    (Array.isArray(value) || isPlainObject(value)) &&
    Object.isExtensible(value) &&
    !value._isVue
  ) {
    // 为 value 实例化一个 Observer 实例
    ob = new Observer(value)
  }
  if (asRootData && ob) {
    ob.vmCount++
  }
  return ob
}
复制代码

initUse() 方法 —— 初始化 Vue.use

文件位置:src\core\global-api\use.js

如果插件是一个对象,必须提供 install 方法.

如果插件是一个函数,且这个函数对象上没有 install 属性的函数,那么它会被作为 install 方法,如果这个函数对象上有一个 install 属性的函数,那么会优先使用 install 属性.

install 方法调用时,会将 Vue 作为参数传入,该方法需要在调用 new Vue() 之前被调用.

export function initUse (Vue: GlobalAPI) {
  // 一般用于注册插件,plugin 可以是 Function,也可以是对象 { install: (Vue)=>{} } 
  Vue.use = function (plugin: Function | Object) {
    const installedPlugins = (this._installedPlugins || (this._installedPlugins = []))
    // 避免重复注册插件
    if (installedPlugins.indexOf(plugin) > -1) {
      return this
    }
    // additional parameters
    const args = toArray(arguments, 1)
    // 将 Vue 作为插件参数中的第一个参数
    args.unshift(this)
    // plugin 对象上存在 install 属性,且值为函数,通过 apply 绑定 this 并进行调用
    if (typeof plugin.install === 'function') {
      plugin.install.apply(plugin, args)
    } else if (typeof plugin === 'function') {
      // plugin 本身是函数,通过 apply 进行调用
      plugin.apply(null, args)
    }
    // 保存插件到 Vue.installedPlugins 数组中
    installedPlugins.push(plugin)
    return this
  }
}
复制代码

initMixin() 方法 —— 初始化 Vue.mixin

文件位置:src\core\global-api\mixin.js

全局注册一个混入,影响注册之后所有创建的每个 Vue 实例,不推荐使用. 使用最多的还是组件配置项上的 mixins 选项.

本质就是合并配配置项,即 mergeOptions() 方法.

export function initMixin (Vue: GlobalAPI) {
  // 向组件配置中进行混入配置项
  Vue.mixin = function (mixin: Object) {
    // 本质就是合并配配置项
    this.options = mergeOptions(this.options, mixin)
    return this
  }
}


目录
相关文章
|
8天前
|
移动开发 JavaScript 前端开发
vue源码如何学习?
【4月更文挑战第20天】Vue.js源码学习概要:首先,需深入了解Vue基础知识、JavaScript(ES6+)及Node.js+npm。从GitHub克隆Vue源码仓库,安装依赖并构建。学习路径始于`entry-runtime-with-compiler.js`,然后深入`core/observer`、`vdom`、`renderer`和`instance`模块,理解响应式系统、虚拟DOM、渲染及实例创建。此外,研究`src/compiler`以了解模板编译。学习过程需耐心阅读、注解代码,结合相关资源辅助理解。
26 0
|
8天前
|
JavaScript API
vue 3.0 所采用的 Composition Api 和 vue 2.0 使用的 Option Api 区别
vue 3.0 所采用的 Composition Api 和 vue 2.0 使用的 Option Api 区别
40 0
|
6天前
|
设计模式 JavaScript API
Vue.js的provide/inject API实现了依赖注入
【5月更文挑战第17天】Vue.js的provide/inject API实现了依赖注入,允许父组件向深层子组件传递依赖,降低耦合,提高代码可维护性和测试性。通过provide选项提供依赖,如`provide: {foo: &#39;foo&#39;, bar: this.bar}`,子组件通过inject选项接收,如`inject: [&#39;foo&#39;, &#39;bar&#39;]`。适用于跨组件共享数据、插件开发和高阶组件。然而,应谨慎使用以防止过度复杂化代码结构。
14 0
|
5天前
|
监控 安全 NoSQL
采用java+springboot+vue.js+uniapp开发的一整套云MES系统源码 MES制造管理系统源码
MES系统是一套具备实时管理能力,建立一个全面的、集成的、稳定的制造物流质量控制体系;对生产线、工艺、人员、品质、效率等多方位的监控、分析、改进,满足精细化、透明化、自动化、实时化、数据化、一体化管理,实现企业柔性化制造管理。
29 3
|
6天前
|
JavaScript 架构师 API
Vue 3.x全面升级指南:Composition API深度探索
Vue 3.x 的全面升级引入了 Composition API,这是对 Vue 2.x 传统 Options API 的一个重大改进,它提供了更加灵活和模块化的代码组织方式.
12 0
|
8天前
vue3版本的爱心源码
vue3版本的爱心源码
9 0
|
8天前
|
JavaScript 前端开发 BI
采用前后端分离Vue,Ant-Design技术开发的(手麻系统成品源码)适用于三甲医院
开发环境 技术架构:前后端分离 开发语言:C#.net6.0 开发工具:vs2022,vscode 前端框架:Vue,Ant-Design 后端框架:百小僧开源框架 数 据 库:sqlserver2019
28 4
采用前后端分离Vue,Ant-Design技术开发的(手麻系统成品源码)适用于三甲医院
|
8天前
|
JavaScript 前端开发 API
组合API:掌握Vue的组合式API(Composition API)
【4月更文挑战第24天】Vue.js的组合式API是Vue 3中的新特性,旨在提供更灵活的组件逻辑组织方式。它允许开发者像React Hooks一样定义和复用逻辑单元——组合函数。通过组合函数,可以跨组件共享和管理状态,提升代码复用和维护性。本文介绍了如何开始使用组合式API,包括安装Vue CLI、引入API、使用组合函数以及组织和复用逻辑。掌握组合式API能增强开发复杂应用的能力,改善代码结构和可读性。
|
8天前
|
JSON JavaScript API
访问REST API:在Vue中消费和管理远程数据
【4月更文挑战第23天】本文探讨了在Vue应用中高效访问REST API的方法,包括选择合适的API、使用Axios或Fetch发送请求、封装API服务、处理响应和数据、错误管理及性能优化。关键点在于创建服务层封装请求,使用计算属性和方法处理数据,以及实施错误处理和性能提升策略。通过这些最佳实践,开发者能更好地管理和消费远程数据,构建出动态、响应式的Vue应用。
|
8天前
|
JavaScript API
Vue 组合式 API
Vue 组合式 API