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

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

mergeOptions() 方法

文件位置:src\core\util\options.js

主要就是将两个选项对象合并为一个新对象,也是实例化和继承的核心.

export function mergeOptions (
  parent: Object,
  child: Object,
  vm?: Component
): Object {
  if (process.env.NODE_ENV !== 'production') {
    checkComponents(child)
  }
  // 如果 child 是函数,就送函数上获取配置项
  if (typeof child === 'function') {
    child = child.options
  }
  // 对选项配置进行标准化
  normalizeProps(child, vm)
  normalizeInject(child, vm)
  normalizeDirectives(child)
  // 在子选项上进行 extends (扩展) 和 mixins (混合)
  // 并且必须是原始配置对象,也就没有经过合并的配置对象
  // 而每个被合并过的配置对象上都存在 _base 属性
  if (!child._base) {
    // 组件的 extents 和 Vue.extend 是一样的,extents 为了便于扩展单文件组件
    // var CompA = { ... } , var CompB = { extends: CompA, ... }, B 继承了 A
    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) {
    // 选项合并策略
    // 默认策略为:如果 child 有值,就会覆盖 parent 值,否则使用 parent 值
    const strat = strats[key] || defaultStrat
    options[key] = strat(parent[key], child[key], vm, key)
  }
  return options
}
复制代码

Vue.extend() 方法

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

使用基础 Vue 构造器,创建一个 子类,参数是一个包含组件选项的对象,data 选项是特例,因为在 Vue.extend() 中它必须是函数.

export function initExtend (Vue: GlobalAPI) {
  /**
   * Each instance constructor, including Vue, has a unique
   * cid. This enables us to create wrapped "child
   * constructors" for prototypal inheritance and cache them.
   */
  Vue.cid = 0
  let cid = 1
  /**
   * Class inheritance
   * 使用 Vue.extend,创建一个子类,参数是一个包含组件选项的对象
   */
  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]
    }
    // 校验组件名称
    const name = extendOptions.name || Super.options.name
    if (process.env.NODE_ENV !== 'production' && name) {
      validateComponentName(name)
    }
    /*
     核心:定义一个 Vue 子类,本质上和 Vue 构造函数一样
      function Vue (options) {
        this._init(options)
      }
    */
    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
    // For props and computed properties, we define the proxy getters on
    // the Vue instances at extension time, on the extended prototype. This
    // avoids Object.defineProperty calls for each instance created.
    // 将 props 代理到子类上,在子类中可以直接通过 this.props 形式访问
    if (Sub.options.props) {
      initProps(Sub)
    }
    // 将 computed 代理到子类上,在子类中可以直接通过 this.computed 形式访问
    if (Sub.options.computed) {
      initComputed(Sub)
    }
    // allow further extension/mixin/plugin usage
    // 将基类的扩展方法赋值给子类,使子类拥有自己的扩展能力
    Sub.extend = Super.extend
    Sub.mixin = Super.mixin
    Sub.use = Super.use
    // create asset registers, so extended classes
    // can have their private assets too.
    ASSET_TYPES.forEach(function (type) {
      Sub[type] = Super[type]
    })
    // enable recursive self-lookup
    // 组件递归自调用的原理
    // { name:'comp', components: { Comp } }
    if (name) {
      Sub.options.components[name] = Sub
    }
    // keep a reference to the super options at extension time.
    // later at instantiation we can check if Super's options have
    // been updated.
    Sub.superOptions = Super.options
    Sub.extendOptions = extendOptions
    Sub.sealedOptions = extend({}, Sub.options)
    // cache constructor
    // 缓存子类
    cachedCtors[SuperId] = Sub
    return Sub
  }
}
复制代码

initAssetRegisters() 方法

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

这个方法就是负责初始化 Vue.component()Vue.directive()Vue.filter() 方法,因为这三个 api 实现上比较特殊,但是原理又很相似,所以就统一放在 initAssetRegisters 中进行初始化.

export function initAssetRegisters (Vue: GlobalAPI) {
  /**
   * Create asset registration methods.
   * 统一初始化 Vue.component, Vue.directive, Vue.filter
   */
  ASSET_TYPES.forEach(type => {
    Vue[type] = function (
      id: string,
      definition: Function | Object
    ): Function | Object | void {
      // 如果对应的 definition 没有传递,直接返回
      if (!definition) {
        return this.options[type + 's'][id]
      } else {
        /* istanbul ignore if */
        // 校验组件名字
        if (process.env.NODE_ENV !== 'production' && type === 'component') {
          validateComponentName(id)
        }
        // 如果是组件
        if (type === 'component' && isPlainObject(definition)) {
          // 设置组件的 name,如果是配置对象就先取 options.name,不存在就取传入的第一个值
          definition.name = definition.name || id
          // 通过 Vue.extend 方法,基于 definition 扩展一个新的组件子类,直接 new definition() 实例化一个组件
          definition = this.options._base.extend(definition)
        }
         // 如果是指令
        if (type === 'directive' && typeof definition === 'function') {
          definition = { bind: definition, update: definition }
        }
        // 将每个配置项都放到根组件的对应配置项中
        // 如:{components:{ id: comonent },directives:{id: directive} ,filters:{ id: filter },... }
        this.options[type + 's'][id] = definition
        return definition
      }
    }
  })
}
复制代码

总结

Vue.use(plugin) 的作用是什么?

  • Vue.use 是用来安装 Vue 插件的
  • 如果插件是一个 对象,必须提供 install 方法
  • 如果插件是一个 函数,它会被作为 install 方法
  • install 方法调用时,会将 Vue 作为参数传入
  • 该方法需要在调用 new Vue() 之前被调用
  • install 方法被同一个插件 多次调用,插件将只会被安装一次

Vue.mixin(options) 的作用是什么?

  • Vue.mixin 本质上就是调用 mergeOptions( parent: Object, child: Object, vm?: Component),作用就是将两个选项对象合并为一个新对象
  • 首先对 props、inject、directives 选项进行标准化处理
  • 处理 options 上的 extendsmixins,最终将它们合并到 全局配置
  • options 配置和 全局配置 进行合并发生选项冲突时,options 配置会覆盖 全局配置

PS:当使用组件配置项 mixins 进行混入配置项时

  • 没有冲突就正常进行合并选项
  • 除了 生命周期 钩子发生冲突时,会在组件对应生命周期钩子前被调用,其他如 methods、componentsdirectives 等选项中发生冲突时以 组件 数据 优先

Vue.component(compName, Comp) 的作用是什么?

Vue.component 用于注册全局组件.

本质上就是将 当前组件配置 注册到全局配置的 components 选项上($root.options.components),然后各个子组件在生成 vnode 时会将全局的 components 选项合并到局部的 components 配置项上,自动实现局部注册组件.

  • Vue.component(compName) 表示获取 compName 的组件构造函数
  • Comp 是组件配置对象,则使用 Vue.extend 方法得到组件构造函数,否则直接进行下一步
  • 在全局配置上添加当前组件信息,this.options.components = { compName:CompConstructor, xxx }

Vue.directive('my-directive', definition) 的作用是什么?

Vue.directive 用于注册或获取全局指令,在每个子组件在生成 vnode 时会将全局的 directives 选项合并到局部的 directives 选项中,如 this.options.directives = { directive:{xxx} }

  • 如果 definition 为空,则会获取指定指令的配置对象
  • 如果 definition 是一个函数,则会在 bindupdate 时调用这个函数,相当于配置对象 { bind: definition, update: definition }

Vue.filter('my-filter', definition) 的作用是什么?

Vue.filter 用于注册或获取全局过滤器,在每个子组件在生成 vnode 时会将全局的 filters 选项合并到局部的 filters 选项中,如 this.options.filters = { filters:{xxx} }

  • 如果 definition 为空,则获取 my-filter 过滤器的回调函数
  • 如果 definition 为存在,则注册 this.options.filters['my-filter'] = definition

Vue.extend(extendOptions) 的作用是什么?

Vue.extend 基于 Vue 创建一个子类,通过 mergeOptions(Super.options, extendOptions) 生成新的配置项作为该子类默认的全局配置,因此子类也可以通过 extend() 方法得到自己的子类。

  • 定义子类构造函数,和基类 Vue 一样也是调用 this._init(options)
  • Vue.optionsextendOptions 合并,如果选项冲突,则 extendOptions 会覆盖 Vue.options 中对应的内容,相当于进行预设
  • 给子类定义和 Vue 一样的全局 API,比如 Sub.extend = Super.extend
  • 向外返回子类 Sub

Vue.set(target, key, val) 和 Vue.delete(target, key) 的作用是什么?

Vue.set(target, key, val) —— 新增或更新对应属性或元素值

  • 向响应式对象中添加一个新的 property,对新属性设置 gettersetter,读取时进行依赖收集,设置时进行依赖更新通知
  • 如果 target 是数组,则 key 需要为数组中对应的 index,本质上是通过重写的 splice 方法进行更新数组元素
  • 如果 target 是对象,则直接更新对应属性数据
  • 如果 target 不是响应式对象,对应操作会成功,但是不会具备响应式
  • 不能向 Vue 实例或者 $data 动态添加根级别的响应式数据Vue.delete(target, key) —— 删除对象的 property
  • 如果 target 是响应式对象,确保删除对应属性后能触发更新视图
  • 如果 target 是数组,还是通过重写的 splice 方法进行删除元素
  • 如果 target 是对象,则通过 delete target[key] + ob.dep.notify() 实现删除并更新视图


目录
相关文章
|
监控 Java 应用服务中间件
高级java面试---spring.factories文件的解析源码API机制
【11月更文挑战第20天】Spring Boot是一个用于快速构建基于Spring框架的应用程序的开源框架。它通过自动配置、起步依赖和内嵌服务器等特性,极大地简化了Spring应用的开发和部署过程。本文将深入探讨Spring Boot的背景历史、业务场景、功能点以及底层原理,并通过Java代码手写模拟Spring Boot的启动过程,特别是spring.factories文件的解析源码API机制。
345 2
|
11月前
|
存储 人工智能 API
AgentScope:阿里开源多智能体低代码开发平台,支持一键导出源码、多种模型API和本地模型部署
AgentScope是阿里巴巴集团开源的多智能体开发平台,旨在帮助开发者轻松构建和部署多智能体应用。该平台提供分布式支持,内置多种模型API和本地模型部署选项,支持多模态数据处理。
5942 77
AgentScope:阿里开源多智能体低代码开发平台,支持一键导出源码、多种模型API和本地模型部署
|
JavaScript API
vue中api统一管理
【10月更文挑战第4天】
|
9月前
|
存储 API 文件存储
单页图床HTML源码+本地API接口图床系统源码
图床系统是一种用于存储和管理图片文件的在线服务。它允许用户上传图片文件,并生成相应的图片链接,从而方便用户在网页、社交媒体或其他平台上分享图片。
375 2
单页图床HTML源码+本地API接口图床系统源码
|
JavaScript 前端开发 API
Vue.js 3:探索组合式API带来的新变革
Vue.js 3:探索组合式API带来的新变革
373 84
|
JavaScript 前端开发 API
Vue.js 3:深入探索组合式API的实践与应用
Vue.js 3:深入探索组合式API的实践与应用
|
JavaScript 前端开发 API
Vue.js 3中的Composition API:提升你的组件开发体验
Vue.js 3中的Composition API:提升你的组件开发体验
366 1
|
存储 数据可视化 JavaScript
可视化集成API接口请求+变量绑定+源码输出
可视化集成API接口请求+变量绑定+源码输出
328 4
|
JavaScript 前端开发 API
探索Vue.js 3的组合式API:一种更灵活的组件状态管理方式
【10月更文挑战第5天】探索Vue.js 3的组合式API:一种更灵活的组件状态管理方式
|
JSON 资源调度 JavaScript
Vue框架中Ajax请求的实现方式:使用axios库或fetch API
选择 `axios`还是 `fetch`取决于项目需求和个人偏好。`axios`提供了更丰富的API和更灵活的错误处理方式,适用于需要复杂请求配置的场景。而 `fetch`作为现代浏览器的原生API,使用起来更为简洁,但在旧浏览器兼容性和某些高级特性上可能略显不足。无论选择哪种方式,它们都能有效地在Vue应用中实现Ajax请求的功能。
328 4