vue2-全局api源码分析

665 阅读4分钟

本文已参与「掘力星计划」,赢取创作大礼包,挑战创作激励金。

1. 前言

官方文档 分析Vue的源码, 总是会遇到这些全局api的调用, 所以先把它分析一下.

全局api就是挂载在Vue上的静态方法, 大致有以下这些

  1. set 新增或修改对象一个属性的值, 使得这个property也是响应式的, 且触发视图更新
  2. delete 删除对象的一个属性, 并且触发更新
  3. nextTick 将回调函数统一放到一个数组里, 等下一个tick, 按照添加的顺序统一逐个回调
  4. observable 将一个对象变为响应式的
  5. use 注册插件
  6. mixin 混入选项
  7. extend 生成Vue子类

剩下的三个 'component', 'directive', 'filter' 后面专门写文章

2. set

官方文档

这个方法同时挂载到了Vue的静态上和实例上

// 静态上
Vue.set = set
// 实例上
Vue.prototype.$set = set
//
/**
 * Set a property on an object. Adds the new property and
 * triggers change notification if the property doesn't
 * already exist.
 */
export function set(
  target: Array<any> | Record<string, any>,
  key: any,
  val: any
): any {
  // 设置数组索引的值
  if (Array.isArray(target) && isValidArrayIndex(key)) {
    // 可能设置的index大于数组的length
    target.length = Math.max(target.length, key)
    // 数组的splice方法可以监听到修改
    target.splice(key, 1, val)
    return val
  }
  if (key in target && !(key in Object.prototype)) {
    // 如果 key是target的本就有的属性, 直接赋值即可, 因为 target 是响应式的了
    target[key] = val
    return val
  }
  const ob = (target as any).__ob__
  if (!ob) {
    // target 不是响应式的值, 直接赋值结束
    target[key] = val
    return val
  }
  // 把val也弄成响应式的
  defineReactive(ob.value, key, val)
  // target的触发一下通知
  ob.dep.notify()
  return val
}

总结: 它用来给对象的属性赋值, 如果该对象是响应式的, 会触发它的更新通知

注意点: 不能给Vue实例赋值, 也不能给$data赋值, 为了稳定性, 防止造成应用异常

3. delete

官方文档

这个方法同时挂载到了Vue的静态上和实例上

// 静态上
Vue.delete = del
// 实例上
Vue.prototype.$delete = del
/**
 * Delete a property and trigger change if necessary.
 */
export function del(target: Array<any> | Object, key: any) {
  if (Array.isArray(target) && isValidArrayIndex(key)) {
    // 数组的方法删除, 修改会触发更新
    target.splice(key, 1)
    return
  }
  const ob = (target as any).__ob__

  if (!hasOwn(target, key)) {
    // 删除的属性不存在
    return
  }
  delete target[key]
  if (!ob) {
    // 被删属性的对象不是响应式的
    return
  }
  // 如果是响应式的, 触发一下更新
  ob.dep.notify()
}

总结: 它是用来删除一个对象的属性的, 如果这个对象是响应式的, 会触发它的更新通知

注意点: 不能删除Vue实例的属性, 也不能删除$data上的属性, 为了稳定性, 防止造成应用异常

4. nextTick

参见我之前写的文章

5. extend

官方文档

这个方法只挂载到了Vue静态上

/**
 * 通过基础Vue构造器, 创建一个"子类", 这里并不是严格意义上的子类, 因为并不是继承得到的
 * @param extendOptions 扩展的组件 options
 * @returns 返回扩展的子类
 */
Vue.extend = function (extendOptions: any): Component {
  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

  // 定义子类, 最后会返回这个子类
  const Sub = (function VueComponent(this: any, options: any) {
    this._init(options)
  } as unknown) as Component
  // 用这种方式继承父类的原型, 修改子类的原型不会影响父类, 修改父类的原型会影响到子类原型方法的查找
  Sub.prototype = Object.create(Super.prototype)
  // 修正 constructor 指向, 不然就指向父类了
  Sub.prototype.constructor = Sub
  // 每一个Vue类(子类)都有一个 cid,用来标识唯一,  Vue 的cid是0, 然后构建的子类会递增
  Sub.cid = cid++
  // 合并父类的选项到子类上
  Sub.options = mergeOptions(Super.options, extendOptions)
  // 指定父类, 只在一个地方用到了, _init 中 resolveConstructorOptions
  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.
  if (Sub.options.props) {
    initProps(Sub)
  }
  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
  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
}

总结: 用来扩展子类, 所有的自定义组件是通过这个方法得到的

以后分析组件的时候, 再详细往下分析

6. use

官方文档

这个方法只挂载到了Vue静态上

Vue.use = function (plugin: Function | any) {
  // 读取缓存
  const installedPlugins =
    this._installedPlugins || (this._installedPlugins = [])
  // 缓存中有, 证明已安装过这个插件, 直接返回
  if (installedPlugins.indexOf(plugin) > -1) {
    return this
  }

  // additional parameters
  // 得到 [this, ...arguments]
  const args = toArray(arguments, 1)
  args.unshift(this)
  // 插件执行时, 得到的第一个参数是 Vue , 剩下的是安装插件时传入的
  if (typeof plugin.install === 'function') {
    plugin.install.apply(plugin, args)
  } else if (typeof plugin === 'function') {
    plugin.apply(null, args)
  }
  // 把插件缓存起来
  installedPlugins.push(plugin)
  return this
}

Vue内部的代码中, 没有安装过任何一个插件

我们可以把给Vue任何增强功能封装成"插件".

7. mixin

官方文档

注册一个全局混入, 代码及其简单

Vue.mixin = function (mixin: Object) {
  // 将传入的mixin合并到 options 上
  this.options = mergeOptions(this.options, mixin)
  return this
}

Vue内部的代码中, 没有混入过任何选项

8. compile

官方文档

将模板字符串编译成 render 函数

Vue.compile = compileToFunctions

这是个大坑, 留着以后细细写

9. observable

官方文档

将一个对象变成响应式的

Vue.observable = <T>(obj: T): T => {
  observe(obj)
  return obj
}

响应式对象, 就是读取它的属性会被它知道, 当它的属性变化了, 它会告诉读取了它属性的地方, 如果这个地方有需要, 就可以做一些事情了, 例如在 render 中, 会触发重新 render, 在 computed 中, 会触发重新计算, 在 watch 中, 会触发函数重新执行, 就这仨地方, 其它地方就属于没有需要的地方, 在Vue中, 通过Dep.target标识是否有需要.

这里也留个坑

10. 最后

这里只是初略的写了一下Vue上面的静态方法, 留下了三(五)个大坑, 后面会补上.

附上我之前写的几篇文章

  1. vue2 源码解析之 nextTick
  2. 代码片段之 js 限流调度器
  3. 数据结构与算法之链表(1)
  4. vue2 源码解析之事件系统$on