Vue2 源码解读一:Global API

文章目录

Vue2 源码解读一:Global API
  1、准备源码,添加调试
  2、Flow静态类型检查
  3、Global API
  4、global-api/index.js
  5、global-api/assets.js
  6、global-api/extend.js
  7、global-api/mixin.js
  8、global-api/use.js
Vue2 源码解读二:mergeOptions
Vue2 源码解读三:Vue实例
Vue2 源码解读四:Observer模块


Vue2 源码解读一:GlobalAPI

1、准备源码,添加调试

  1. 下载Vue2 源码 Vue Release
  2. 安装依赖cnpm install,使用cnpm安装;
  3. 修改package.json中的scripts,dev脚本添加--sourcemap,改为"dev": "rollup -w -c scripts/config.js --environment TARGET:web-full-dev --sourcemap"
  4. 启动dev脚本cnpm run dev
  5. 在demo中引用 源码目录/dist/vue.js,即可通过F12,在Sources中给源码添加断点调试了;
  6. 入口文件./src/core/index.js

2、Flow静态类型检查

Flow 是FaceBook发布的开源JavaScript 静态类型检查器。

Vue 2.x版本使用Flow来做静态类型检查,语法类似于TypeScript,在flow文件夹中是一些类型定义文件,详细使用请查看Flow官网

Vue 3.x版本已使用TypeScript重写,Flow也随之淘汰。

3、Global API

先看一下入口文件src/core/index.js的代码:

import Vue from './instance/index'
import { initGlobalAPI } from './global-api/index'
import { isServerRendering } from 'core/util/env'
import { FunctionalRenderContext } from 'core/vdom/create-functional-component'

// 初始化全局API
initGlobalAPI(Vue)
// 是否服务器端渲染
Object.defineProperty(Vue.prototype, '$isServer', {
  get: isServerRendering
})
// 服务器端渲染上下文
Object.defineProperty(Vue.prototype, '$ssrContext', {
  get () {
    /* istanbul ignore next */
    return this.$vnode && this.$vnode.ssrContext
  }
})
// expose FunctionalRenderContext for ssr runtime helper installation
Object.defineProperty(Vue, 'FunctionalRenderContext', {
  value: FunctionalRenderContext
})
// 指定版本号
Vue.version = '__VERSION__'

export default Vue

上面代码中,主要调用了initGlobalAPI(),所以我们重点关注src/core/global-api文件夹的内容:

文件作用
global-api/assets.js初始化component、directive、filter全局方法
global-api/extend.js初始extend全局方法,Vue.extend()
global-api/mixin.js初始mixin全局方法,Vue.mixin()
global-api/use.js初始use全局方法,Vue.use()
global-api/index.js调用上面的初始化方法,以及添加del、set、nextTick等全局方法

4、global-api/index.js

/* @flow */

import config from '../config'
import { initUse } from './use'
import { initMixin } from './mixin'
import { initExtend } from './extend'
import { initAssetRegisters } from './assets'
import { set, del } from '../observer/index'
import { ASSET_TYPES } from 'shared/constants'
import builtInComponents from '../components/index'
import { observe } from 'core/observer/index'

import {
  warn,
  extend,
  nextTick,
  mergeOptions,
  defineReactive
} from '../util/index'

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.'
      )
    }
  }
  // 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.
  // 扩展的Util方法
  Vue.util = {
    warn,
    extend,
    mergeOptions,
    defineReactive
  }
  // 添加全局的set、del、nextTick
  Vue.set = set
  Vue.delete = del
  Vue.nextTick = nextTick

  // 2.6 explicit observable API
  // 添加显示的全局observable方法
  Vue.observable = <T>(obj: T): T => {
    observe(obj)
    return obj
  }
  // 重置options,以及components、directives、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.options._base = Vue
  // 添加全局组件,keep-alive
  extend(Vue.options.components, builtInComponents)
  // 初始化Vue.use、Vue.mixin、Vue.extend
  initUse(Vue)
  initMixin(Vue)
  initExtend(Vue)
  // 初始化Vue.component、Vue.directive、Vue.filter
  initAssetRegisters(Vue)
}

从入口文件上看,总体分为两个部分:

  • 全局属性
    • config:全局配置对象
    • options:存放初始化的数据,在创建Vue实例时传入的配置对象最终要与这份配置属性合并
  • 全局方法
    • util:暴露了一些辅助方法,官方并不建议使用
    • set:设置响应式数据,触发视图更新。常用在更新数组中单个数据,添加对象属性
    • delete:删除数组元素、删除对象属性,触发视图更新
    • nextTick:结束此轮循环后执行回调。常用与等待dom更新、加载完毕后
    • use:安装插件,会规避重复插件
    • mixin:混合插件的功能
    • extend:创建基于Vue的子类并扩展初始内容
    • component:注册全局组件
    • directive:注册全局指令
    • filter:注册全局过滤器

下面是del、set的实现:

// src/core/observer.js

/**
 * 全局的del方法,Vue.del
 * 数组:通过splice删除,对象:通过delete删除
 * 在必要的时候通知更改
 */
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)) {
  	// 这里的splice已经被Vue拦截,会触发dep.update
    target.splice(key, 1)
    return
  }
  // 获取Vue监听器
  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
  }
  if (!hasOwn(target, key)) {
    return
  }
  // 对象:通过delete移除
  delete target[key]
  if (!ob) {
    return
  }
  // 通知更新
  ob.dep.notify()
}

/**
 * 全局的set方法,Vue.set
 * 给新的属性添加响应式
 */
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)}`)
  }
  // 有效数组,调用splice添加
  if (Array.isArray(target) && isValidArrayIndex(key)) {
    target.length = Math.max(target.length, key)
  	// 这里的splice已经被Vue拦截,会触发dep.update
    target.splice(key, 1, val)
    return val
  }
  // 如果target属性已存在,直接设置即可
  if (key in target && !(key in Object.prototype)) {
    target[key] = val
    return val
  }
  const ob = (target: any).__ob__
  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
  }
  // 如果target不存在监听器,直接设置属性
  if (!ob) {
    target[key] = val
    return val
  }
  // 添加响应式属性。后面再详解
  defineReactive(ob.value, key, val)
  // 通知更新
  ob.dep.notify()
  return val
}

5、global-api/assets.js

/* @flow */

import { ASSET_TYPES } from 'shared/constants'
import { isPlainObject, validateComponentName } from '../util/index'

export function initAssetRegisters (Vue: GlobalAPI) {
  /**
   * Create asset registration methods.
   */
  // ['component', 'directive', 'filter']
  ASSET_TYPES.forEach(type => {
    Vue[type] = function (
      id: string,
      definition: Function | Object
    ): Function | Object | void {
      if (!definition) {
        // 返回指定asset
        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)) {
          // 设置组件名称
          definition.name = definition.name || id
          // 完善组件内容
          definition = this.options._base.extend(definition)
        }
        // 注册指令
        if (type === 'directive' && typeof definition === 'function') {
          // 如果传的是function,则设置bind、update
          definition = { bind: definition, update: definition }
        }
        // 添加到options中
        this.options[type + 's'][id] = definition
        return definition
      }
    }
  })
}

6、global-api/extend.js

/* @flow */

import { ASSET_TYPES } from 'shared/constants'
import { defineComputed, proxy } from '../instance/state'
import { extend, mergeOptions, validateComponentName } from '../util/index'

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.
   */
  // 设置当前组件cid为0
  Vue.cid = 0
  // 递增的子组件ID
  let cid = 1

  /**
   * Class inheritance
   */
  Vue.extend = function (extendOptions: Object): Function {
    extendOptions = extendOptions || {}
    const Super = this
    const SuperId = Super.cid
    // 用扩展选项的_Ctor属性判断是否已扩展
    const cachedCtors = extendOptions._Ctor || (extendOptions._Ctor = {})
    if (cachedCtors[SuperId]) {
      return cachedCtors[SuperId]
    }
    // 设置默认name为父级的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
    // 自增cid
    Sub.cid = cid++
    // 合并父级options和扩展option
    Sub.options = mergeOptions(
      Super.options,
      extendOptions
    )
    // super属性
    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.
    // 初始化子类的prop
    if (Sub.options.props) {
      initProps(Sub)
    }
    // 初始化子类的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
    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.
    // 保留Super的options,在Vue实例化时检测options更新
    Sub.superOptions = Super.options
    Sub.extendOptions = extendOptions
    Sub.sealedOptions = extend({}, Sub.options)

    // cache constructor
    // 缓存子类构造函数
    cachedCtors[SuperId] = Sub
    return Sub
  }
}

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])
  }
}

7、global-api/mixin.js

/* @flow */

import { mergeOptions } from '../util/index'

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

mixin混合,其实就是进行合并options操作。

8、global-api/use.js

/* @flow */
import { toArray } from '../util/index'

export function initUse (Vue: GlobalAPI) {
  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)
    // 把this插入到参数列表的第一个
    args.unshift(this)
    if (typeof plugin.install === 'function') {
      // 如果插件中,包含install方法,则调用插件的install方法
      plugin.install.apply(plugin, args)
    } else if (typeof plugin === 'function') {
      // 如果插件本身就是一个方法,则直接调用
      plugin.apply(null, args)
    }
    // 标记此插件已注册
    installedPlugins.push(plugin)
    return this
  }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值