Vue2.x 源码 - 初始化:全局API

上一篇:Vue2.x 源码 - 初始化:实例挂载($mount)的实现

Vue 类在定义之后一直到被入口暴露出来会经历多次的再次封装,这里我们就先看看第一次被二次封装时往 Vue 这个类上都添加了那些东西;在 src/core/index.js 文件里::

//混入全局API
initGlobalAPI(Vue)
//响应式绑定服务端渲染相关属性和方法
Object.defineProperty(Vue.prototype, '$isServer', {
  get: isServerRendering
})
Object.defineProperty(Vue.prototype, '$ssrContext', {
  get () {
    return this.$vnode && this.$vnode.ssrContext
  }
})
// 为ssr运行时助手安装暴露FunctionalRenderContext
Object.defineProperty(Vue, 'FunctionalRenderContext', {
  value: FunctionalRenderContext
})
//版本
Vue.version = '__VERSION__'
//再次暴露Vue
export default Vue

这里主要是执行 initGlobalAPI 混入一些全局的 API;在 src/core/global-api/index.js 文件中:

export function initGlobalAPI (Vue: GlobalAPI) {
  // config
  const configDef = {}
  configDef.get = () => config
  if (process.env.NODE_ENV !== 'production') {
    configDef.set = () => {
      warn(
        'Do not replace the Vue.config object, set individual fields instead.'
      )
    }
  }
  Object.defineProperty(Vue, 'config', configDef)
  // 公开实效的方法。
  // 注意:这些不被认为是公共API的一部分-避免依赖它们,除非你意识到风险。
  Vue.util = {
    warn,
    extend,
    mergeOptions,
    defineReactive
  }
  Vue.set = set
  Vue.delete = del
  Vue.nextTick = nextTick
  // 2.6 explicit observable API
  Vue.observable = <T>(obj: T): T => {
    observe(obj)
    return obj
  }
  Vue.options = Object.create(null)
  ASSET_TYPES.forEach(type => {
    Vue.options[type + 's'] = Object.create(null)
  })
  // 这用于标识“基”构造函数,以便在Weex的多实例场景中扩展所有普通对象组件。
  Vue.options._base = Vue
  extend(Vue.options.components, builtInComponents)
  initUse(Vue)
  initMixin(Vue)
  initExtend(Vue)
  initAssetRegisters(Vue)
}

1、Vue.config

这里劫持了 Vue 的 config 属性,使的无法对其进行修改;

export default ({
  //选择合并策略
  optionMergeStrategies: Object.create(null),
  //是否取消警告
  silent: false,
  //在启动时显示生产模式提示信息
  productionTip: process.env.NODE_ENV !== 'production',
  //是否启用devtools
  devtools: process.env.NODE_ENV !== 'production',
  //是否记录性能
  performance: false,
  //监视器错误的错误处理程序
  errorHandler: null,
  //观察程序警告的警告处理程序
  warnHandler: null,
  //忽略某些自定义元素
  ignoredElements: [],
  //v - on的自定义用户keyCode
  keyCodes: Object.create(null),
  //检查是否保留了标记,以便它不能注册为组件。这取决于平台,可能会被覆盖
  isReservedTag: no,
  //检查属性是否被保留,以便不能用作组件道具。这取决于平台,可能会被覆盖
  isReservedAttr: no,
  //检查标记是否为未知元素。取决于平台
  isUnknownElement: no,
  //获取元素的名称空间
  getTagNamespace: noop,
  //解析特定平台的真实标签名称
  parsePlatformTagName: identity,
  //检查是否必须使用属性(例如值)绑定属性。这个取决于平台
  mustUseProp: no,
  //异步执行更新。如果设置为false,将显著降低性能
  async: true,
  //因遗留原因暴露
  _lifecycleHooks: LIFECYCLE_HOOKS
}: Config)

是 Vue 全局的配置对象,可以在引导应用程序之前修改上面列出的属性。

2、Vue.set

Vue.set 方法与 Vue.prototype.$set 方法一样,都是 set 方法;在 src/core/observer/index 文件里面:

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)}`)
  }
  //target是不是数组,key是不是有效的数组索引
  if (Array.isArray(target) && isValidArrayIndex(key)) {
    //对比key和target.length来重新赋值数组长度
    target.length = Math.max(target.length, key)
    //替换key位置的值
    target.splice(key, 1, val)
    return val
  }
  //如果key已经存在target里面或key属于Object的原生属性,那么直接赋值
  if (key in target && !(key in Object.prototype)) {
    target[key] = val
    return val
  }
  //避免在运行时向Vue实例或其根$data添加响应性属性——>在data选项中预先声明它
  //获取observe实例,可参考Observe实例对象
  const ob = (target: any).__ob__
  //isVue为true说明target是Vue实例
  //vmCount属性存在说明是根$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
  }
  if (!ob) {
    target[key] = val
    return val
  }
  //添加成响应式属性,并通知数据更新
  defineReactive(ob.value, key, val)
  ob.dep.notify()
  return val
}

向响应式对象添加属性,确保新属性也是响应式的,从而触发视图更新。目标对象不能是 Vue 实例,也不能是 Vue 实例的根数据对象。

3、Vue.delete

Vue.delete方法与 Vue.prototype.$delete方法一样,都是 set 方法;在 src/core/observer/index 文件里面:

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)}`)
  }
  //target是不是数组,key是不是有效的数组索引
  if (Array.isArray(target) && isValidArrayIndex(key)) {
    //直接删除key位置的值
    target.splice(key, 1)
    return
  }
  //避免删除Vue实例上的属性,或者将其根$data设置为空即可
  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
  }
  //如果target里面没有key这个属性直接返回
  if (!hasOwn(target, key)) {
    return
  }
  //delete 属性,并通知数据更新
  delete target[key]
  if (!ob) {
    return
  }
  ob.dep.notify()
}

删除对象上的属性。如果对象是响应式的,要确保删除会触发视图更新。这主要用于解决 Vue 无法检测属性删除的限制。目标对象不能是 Vue 实例,也不能是 Vue 实例的根数据对象。

4、Vue.nextTick

src/core/util/next-tick.js 文件里面,将回调延迟到下次 DOM 更新循环之后执行;

const callbacks = []
let pending = false
export function nextTick (cb?: Function, ctx?: Object) {
  let _resolve
  // callback是上面定义的一个数组,存储下面方法
  callbacks.push(() => {
    // 回调函数存在则绑定到 vue实例上cb.call(ctx) 、_resolve(ctx)
    if (cb) {
      try {
        cb.call(ctx)
      } catch (e) {
        handleError(e, ctx, 'nextTick')
      }
    } else if (_resolve) {
      _resolve(ctx)
    }
  })
  //padding状态位
  if (!pending) {
    pending = true
    timerFunc()
  }
  // 如果回调函数不存在,当前环境支持 Promise,则返回一个 Promise 对象,并赋值给 _resolve
  if (!cb && typeof Promise !== 'undefined') {
    return new Promise(resolve => {
      _resolve = resolve
    })
  }
}
let timerFunc
//当前环境是否支持promise
if (typeof Promise !== 'undefined' && isNative(Promise)) {
  const p = Promise.resolve()
  timerFunc = () => {
    p.then(flushCallbacks)
    // 设置空计数器,强制刷新微任务队列
    if (isIOS) setTimeout(noop)
  }
  isUsingMicroTask = true
} else if (!isIE && typeof MutationObserver !== 'undefined' && (
  isNative(MutationObserver) ||
  // PhantomJS and iOS 7.x
  MutationObserver.toString() === '[object MutationObserverConstructor]'
)) {
  // 在原生Promise不可用的地方使用MutationObserver
  let counter = 1
  const observer = new MutationObserver(flushCallbacks)
  const textNode = document.createTextNode(String(counter))
  observer.observe(textNode, {
    characterData: true
  })
  timerFunc = () => {
    counter = (counter + 1) % 2
    textNode.data = String(counter)
  }
  isUsingMicroTask = true
} else if (typeof setImmediate !== 'undefined' && isNative(setImmediate)) {
  timerFunc = () => {
    setImmediate(flushCallbacks)
  }
} else {
  timerFunc = () => {
    setTimeout(flushCallbacks, 0)
  }
}
// 将padding标志位置空,数据缓存到copies里面,清空callbacks数组,遍历执行每一个方法
function flushCallbacks () {
  pending = false
  const copies = callbacks.slice(0)
  callbacks.length = 0
  for (let i = 0; i < copies.length; i++) {
    copies[i]()
  }
}

将回调函数缓存在 callbacks 数组里面,然后 调用 timerFunc 方法,timerFunc 会根据不同环境通过不同的手段调用 flushCallbacks 方法来执行每一个缓存在 callbacks 里面的方法。

nextTick的核心思路就是通过异步方法来处理任务;vue 会根据当前环境优先使用 promise.then、MutationObserver 、setImmediate,如果都不支持就使用 setTimeout 把函数延迟到 DOM 更新之后再使用。(原因是宏任务消耗大于微任务,优先使用微任务,最后使用消耗最大的宏任务)

5、Vue.observable

2.6 版本以后新增,主要调用 observe 方法,让一个对象可响应;Vue 内部会用它来处理 data 函数返回的对象。

export function observe (value: any, asRootData: ?boolean): Observer | void {
  if (!isObject(value) || value instanceof VNode) {
    return
  }
  let ob: Observer | void
  //是否存在 _ob_属性,是不是已经存在 Observer:说明已经是响应式
  if (hasOwn(value, '__ob__') && value.__ob__ instanceof Observer) {
    ob = value.__ob__
   //shouldObserve 为true、不是服务端渲染、数组或者对象、对象可拓展、不是根节点
  } else if (shouldObserve && !isServerRendering() && (Array.isArray(value) || isPlainObject(value)) && Object.isExtensible(value)  && !value._isVue
  ) {
    ob = new Observer(value)
  }
  if (asRootData && ob) {
    ob.vmCount++
  }
  return ob
}

判断是否存在 _ob_ 以及value.__ob__ 是否在 Observer 中,这说明当前数据已经是响应式的,所以直接赋值就可以了;否则调用 Observer 将对象设置为响应式;

6、initAssetRegisters(Vue.directive、Vue.filter、Vue.component)

src/core/global-api/assets.js 文件里

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,则表示是获取,直接return
      if (!definition) {
        return this.options[type + 's'][id]
      } else {
        // 注册组件,检查组件名是否规范
        if (process.env.NODE_ENV !== 'production' && type === 'component') {
          validateComponentName(id)
        }
        if (type === 'component' && isPlainObject(definition)) {
          //在definition中没有传name,id就是组件的name
          definition.name = definition.name || id
          //把definition传入extend,返回Vue的子构造器,所以component的第二个参数可以是Vue.extend函数
          definition = this.options._base.extend(definition)
        }
        if (type === 'directive' && typeof definition === 'function') {
          //默认 bind和 update都是入参函数
          definition = { bind: definition, update: definition }
        }
        this.options[type + 's'][id] = definition
        return definition
      }
    }
  })
}

一次挂载 Vue.component()、Vue.directive()和Vue.filter()方法;方法里面区分读取和设置操作;获取操作直接返回参数里面对应的属性值;设置操作则是在 options 参数上设置对应的属性名和属性值;

7、Vue.use

注册使用插件或者组件,在 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
    }
    //处理入参,将第一个参数之后的参数归集,并在首部插入 this 上下文;
    const args = toArray(arguments, 1)
    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
  }
}

安装 Vue.js 插件。如果插件是一个对象,它必须公开一个install方法。如果它是一个函数本身,它将被视为安装方法。install 方法将以 Vue 作为参数被调用。

8、Vue.mixin

将传入的参数对象和vue实例的选项实行合并,在 src/core/global-api/mixin.js 文件里

export function initMixin (Vue: GlobalAPI) {
  Vue.mixin = function (mixin: Object) {
    this.options = mergeOptions(this.options, mixin)
    return this
  }
}

全局应用 mixin,这会影响之后创建的每个 Vue 实例。使用插件时可以使用它来将自定义行为注入到组件中。不推荐全局使用。

9、Vue.extend

创建 Vue 构造器,是一个“子类”,需要通过 $mount 挂载到指定元素上。在 src/core/global-api/extend.js 文件里

export function initExtend (Vue: GlobalAPI) {
  //每个实例构造函数(包括Vue)都有一个唯一的cid。这使我们能够为原型继承创建包装的“子构造函数”并缓存它们。
  Vue.cid = 0
  let cid = 1
  //类继承
  Vue.extend = function (extendOptions: Object): Function {
    extendOptions = extendOptions || {}
    const Super = this //父类
    const SuperId = Super.cid // 父类的cid
    //给 extendOptions添加一个_Ctor 属性
    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)
    }
    //定义子构造函数 Sub
    const Sub = function VueComponent (options) {
      this._init(options)
    }
    //将父类的原型继承到子类中
    Sub.prototype = Object.create(Super.prototype)
    Sub.prototype.constructor = Sub
    //子类递增添加cid
    Sub.cid = cid++
    //合并参数
    Sub.options = mergeOptions(
      Super.options,
      extendOptions
    )
    Sub['super'] = Super
    // 对于props和computed,我们在扩展时在扩展原型上的Vue实例上定义代理getter。这避免了对每个创建的实例调用Object.defineProperty。
    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
    // 注册 directive、filter、component
    ASSET_TYPES.forEach(function (type) {
      Sub[type] = Super[type]
    })
    // enable recursive self-lookup
    if (name) {
      Sub.options.components[name] = Sub
    }
    // 在扩展时保持对超级选项的引用。稍后在实例化时,我们可以检查Super的选项是否已经更新。
    Sub.superOptions = Super.options
    Sub.extendOptions = extendOptions
    Sub.sealedOptions = extend({}, Sub.options)
    // 缓存constructor
    cachedCtors[SuperId] = Sub
    return Sub
  }
}
//将props代理到 _props,vm.name实际上可以访问到的是Sub.prototype._props.name
function initProps (Comp) {
  const props = Comp.options.props
  for (const key in props) {
    proxy(Comp.prototype, `_props`, key)
  }
}
//将传进来的 userDef 判断是函数还是对象,分别定义;然后使用 Object.defineProperty 挂载到 vm 上;
function initComputed (Comp) {
  const computed = Comp.options.computed
  for (const key in computed) {
    defineComputed(Comp.prototype, key, computed[key])
  }
}

Vue.extend 使⽤⼀种⾮常经典的原型继承的⽅式把⼀个纯对 象转换⼀个继承于 Vue 的构造器 Sub 并返回,然后对 Sub 这个对象本⾝扩展了⼀些属性,如扩 展 options 、添加全局 API 等;并且对配置中的 props 和 computed 做了初始化⼯作;最后对于 这个 Sub 构造函数做了缓存,避免多次执⾏ Vue.extend 的时候对同⼀个⼦组件重复构造。

下面是子类得到的属性:

Sub.cid 
Sub.options
Sub.extend
Sub.mixin
Sub.use
Sub.component
Sub.directive
Sub.filter
// 新增的
Sub.super  // 父类
Sub.superOptions // 父类选项
Sub.extendOptions  // 传入子类选项
Sub.sealedOptions // 子类目前的所有选项(父+自己)
10、Vue.compile

将一个模板字符串编译成 render 函数;只有在 entry-runtime-with-compile 模式下才会挂载;在 src/platforms/web/entry-runtime-with-compile.js 文件中;

const { compile, compileToFunctions } = createCompiler(baseOptions);
export const createCompiler = createCompilerCreator(function baseCompile (
  template: string,
  options: CompilerOptions
): CompiledResult {
  const ast = parse(template.trim(), options)
  if (options.optimize !== false) {
    optimize(ast, options)
  }
  const code = generate(ast, options)
  return {
    ast,
    render: code.render,
    staticRenderFns: code.staticRenderFns
  }
})
Vue.compile = compileToFunctions;

其实就是 compileToFunctions 方法;这个后面编译过程再细说;

11、Vue.version

提供字符串形式的 Vue 安装版本号;在 src/core/index.js 文件中;

以上就是对 Vue 这个对象本身扩展的全局静态方法。

下一篇:Vue2.x 源码 - 编译过程(compile)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值